Pregunta

Estoy creando una base de datos de juguetes en C# para aprender más sobre compiladores, optimizadores y tecnologías de indexación.

Quiero mantener el máximo paralelismo entre las solicitudes (al menos de lectura) para incorporar páginas al grupo de búfer, pero no sé cuál es la mejor manera de lograr esto en .NET.

Aquí hay algunas opciones y los problemas que he encontrado con cada una:

  1. Usar System.IO.FileStream y el BeginRead método

    Pero la posición en el expediente no es un argumento para BeginRead, es una propiedad del FileStream (configurado a través del Seek método), por lo que solo puedo emitir una solicitud a la vez y tengo que bloquear la transmisión mientras dure.(¿O yo?La documentación no está clara sobre lo que sucedería si mantuviera el candado solo entre los Seek y BeginRead llama pero lo soltó antes de llamar EndRead.¿Alguien lo sabe?) Sé cómo hacer esto, pero no estoy seguro de que sea la mejor manera.

  2. Parece haber otra manera, centrada en el System.Threading.Overlapped estructura y P\Invocar a la ReadFileEx funcionar en kernel32.dll.

    Desafortunadamente, hay escasez de muestras, especialmente en lenguajes administrados.Esta ruta (si es que puede funcionar) aparentemente también implica la ThreadPool.BindHandle método y los subprocesos de finalización de IO en el grupo de subprocesos.Tengo la impresión de que esta es la forma autorizada de abordar este escenario en Windows, pero no lo entiendo y no puedo encontrar un punto de entrada a la documentación que sea útil para los no iniciados.

  3. ¿Algo más?

  4. En un comentario, jacob sugiere crear un nuevo FileStream para cada lectura en vuelo.

  5. Lea el archivo completo en la memoria.

    Esto funcionaría si la base de datos fuera pequeña.La base de código es pequeña y hay muchas otras ineficiencias, pero la base de datos en sí no lo es.También quiero estar seguro de que estoy haciendo toda la contabilidad necesaria para manejar una base de datos grande (que resulta ser una gran parte de la complejidad:paginación, clasificación externa, ...) y me preocupa que sea demasiado fácil hacer trampa accidentalmente.

Editar

Aclaración de por qué sospecho con la solución 1:mantener un solo bloqueo desde BeginRead hasta EndRead significa que necesito bloquear a cualquiera que quiera iniciar una lectura solo porque hay otra lectura en progreso.Eso parece incorrecto, porque el hilo que inicia la nueva lectura podría (en general) hacer un poco más de trabajo antes de que los resultados estén disponibles.(En realidad, el solo hecho de escribir esto me ha llevado a pensar en una nueva solución, la puse como nueva respuesta).

¿Fue útil?

Solución

Lo que hicimos fue escribir una pequeña capa alrededor de los puertos de finalización de E/S, ReadFile y el estado GetQueuedCompletion en C++/CLI, y luego volver a llamar a C# cuando se completó la operación.Elegimos esta ruta sobre BeginRead y el patrón de operación asíncrona de C# para proporcionar más control sobre los buffers utilizados para leer desde el archivo (o socket).Esta fue una ganancia de rendimiento bastante grande con respecto al enfoque puramente administrado que asigna un nuevo byte [] en el montón con cada lectura.

Además, hay ejemplos de C++ mucho más completos sobre el uso de puertos de finalización de E/S en Internet.

Otros consejos

No estoy seguro de entender por qué la opción 1 no funcionaría para usted.Tenga en cuenta que no puede tener dos subprocesos diferentes intentando utilizar el mismo FileStream al mismo tiempo; hacerlo definitivamente le causará problemas.BeginRead/EndRead está destinado a permitir que su código continúe ejecutándose mientras se lleva a cabo la operación IO potencialmente costosa, no a permitir algún tipo de acceso multiproceso a un archivo.

Por lo tanto, le sugiero que busque y luego comience a leer.

¿Qué pasa si primero carga el recurso (datos de archivo o lo que sea) en la memoria y luego lo comparte entre subprocesos?Ya que es una base de datos pequeña.- No tendrás tantos problemas con los que lidiar.

Utilice el enfoque n.° 1, pero

  1. Cuando llegue una solicitud, tome el candado A.Úselo para proteger una cola de solicitudes de lectura pendientes.Agréguelo a la cola y devuelva algún resultado asíncrono nuevo.Si esto resulta en la primera adición a la cola, llame al paso 2 antes de regresar.Suelte el bloqueo A antes de regresar.

  2. Cuando se completa una lectura (o se llama en el paso 1), tome el bloqueo A.Úselo para proteger la extracción de una solicitud de lectura de la cola.Tome el candado B.Úselo para proteger el Seek -> BeginRead -> EndRead secuencia.Libere el bloqueo B.Actualice el resultado asíncrono creado en el paso 1 para esta operación de lectura.(Dado que se completó una operación de lectura, llame a esto nuevamente).

Esto resuelve el problema de no bloquear ningún hilo que comience una lectura solo porque hay otra lectura en progreso, pero aun así secuencia las lecturas para que la posición actual del flujo de archivos no se estropee.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top