Cómo usar GNU en paralelo de manera efectiva

8

Supongamos que quiero encontrar todas las coincidencias en el archivo de texto comprimido:

$ gzcat file.txt.gz | pv --rate -i 5 | grep some-pattern

pv --rateutilizado aquí para medir el rendimiento del tubo. En mi máquina es de aproximadamente 420Mb / s (después de la descompresión).

Ahora estoy tratando de hacer grep paralelo usando GNU paralelo.

$ gzcat documents.json.gz | pv --rate -i 5 | parallel --pipe -j4 --round-robin grep some-pattern

Ahora el rendimiento se reduce a ~ 260Mb / s. Y lo que es un parallelproceso más interesante en sí mismo es usar mucha CPU. Más que grepprocesos (pero menos que gzcat).

EDITAR 1 : he probado diferentes tamaños de bloque ( --block), así como diferentes valores para -N/ -Lopciones. Nada me ayuda en este punto.

¿Qué estoy haciendo mal?

Denis Bazhenov
fuente

Respuestas:

9

Estoy realmente sorprendido de que consigas 270 MB / s usando GNU Parallel's --pipe. Mis pruebas generalmente alcanzan un máximo de alrededor de 100 MB / s.

Su cuello de botella es más probable en GNU Paralelo: --pipeno es muy eficiente. --pipepart, sin embargo, es: Aquí puedo obtener del orden de 1 GB / s por núcleo de CPU.

Lamentablemente, existen algunas limitaciones en el uso --pipepart:

  • El archivo debe ser buscable (es decir, sin tubería)
  • Debe poder encontrar el inicio de un registro con --recstart / - recend (es decir, sin archivo comprimido)
  • El número de línea es desconocido (por lo que no puede tener un registro de 4 líneas).

Ejemplo:

parallel --pipepart -a bigfile --block 100M grep somepattern
Ole Tange
fuente
1
Gracias. ¿Hay alguna razón por la cual --pipees ineficiente? Quiero decir, ¿es algún tipo de problema fundamental o más de implementación específica?
Denis Bazhenov
2
Sí: GNU Parallel está escrito en perl, y con --pipecada byte tiene que pasar por el proceso único, que tiene que hacer un poco de procesamiento en cada byte. El --pipepartproceso central nunca ve la mayoría de los bytes: son procesados ​​por trabajos generados. Como son bastantes líneas el cuello de botella --pipe, agradecería a un codificador C / C ++ que reescribiera la parte que luego se ejecutaría para las personas que tienen el compilador C en su camino.
Ole Tange
2

grep es muy efectivo: no tiene sentido ejecutarlo en paralelo. En su comando, solo la descompresión necesita más CPU, pero esto no puede ser paralelo.

Dividir la entrada por paralelo necesita más CPU que obtener líneas coincidentes por grep.

El cambio de situación si desea usar en lugar de grep algo que necesita mucha más CPU para cada línea, entonces el paralelo tendría más sentido.

Si desea acelerar esta operación, mire dónde hay cuellos de botella, probablemente sea descompresión (luego ayuda a usar otra herramienta de descompresión o mejor CPU) o - leer desde el disco (luego ayude a usar otra herramienta de descompresión o un mejor sistema de disco).

Según mi experiencia, a veces es mejor usar lzma (-2 por ejemplo) para comprimir / descomprimir archivos, tiene una compresión más alta que gzip, por lo que es mucho menos lo que se necesita leer en el disco y la velocidad es comparable.

indefinido
fuente
1
De hecho, es mi caso. Se utiliza un proceso Java muy hambriento de CPU en lugar de grep. He simplificado la pregunta un poco. Y aún así, comer en paralelo una gran cantidad de CPU no proporciona mucho trabajo a los procesos de Java.
Denis Bazhenov
1

La descompresión es el cuello de botella aquí. Si la descompresión no está paralelizada internamente, no la logrará usted mismo. Si tiene más de un trabajo como ese, entonces, por supuesto, ejecútelos en paralelo, pero su tubería por sí sola es difícil de paralelizar. Dividir una secuencia en secuencias paralelas casi nunca vale la pena, y puede ser muy doloroso con la sincronización y la fusión. A veces solo tiene que aceptar que múltiples núcleos no ayudarán con cada tarea que esté ejecutando.

En general, la paralelización en shell debería estar principalmente en el nivel de procesos independientes.

Orión
fuente
1
No parece que la descompresión sea un cuello de botella en caso de uso parallel. Estoy de acuerdo en que ciertamente es en el primer caso (sin paralelo), pero en el segundo (con paralelo) el cuello de botella está en el lado paralelo. Esto se desprende de la observación de que el rendimiento se reduce significativamente según lo medido por pv. Si el cuello de botella está en descompresión, el rendimiento no cambiará lo que agregue a la tubería. Supongo que es una definición muy intuitiva del rendimiento, lo que limita más el rendimiento.
Denis Bazhenov
1
Es posible que grep sea tan rápido que termine más rápido de lo que parallelpuede escribir en su tubería. En este caso, la mayoría de los grepprocesos simplemente esperan obtener más, mientras paralleltrabajan las 24 horas para multiplexar los bloques en varias tuberías (que son operaciones de E / S adicionales e incluso pueden bloquear la descompresión si el búfer está lleno). ¿Intentaste también jugar con el --blockparámetro? Su valor predeterminado es que 1Mhasta que un grep obtenga 1Mdatos, el resto casi seguramente ya está terminado. Por lo tanto, volvemos al hecho de que no tiene sentido paralelizar esto.
orion
1
Sí, he probado estas opciones con un tamaño de bloque grande y pequeño. Así como diferentes valores para -N/ -Lopciones. Parece que las opciones predeterminadas están muy cerca del óptimo local que he experimentado :)
Denis Bazhenov
1
Intenta cronometrarlo con y sin pv(con time). De esta manera puede ver si en pvsí mismo lo está ralentizando. Si es así, parallelcopiar datos en tuberías es definitivamente una sobrecarga adicional. Y, en cualquier caso, estoy bastante seguro de que grepes casi en tiempo real en este caso, especialmente si el patrón es una cadena simple sin mucho retroceso. Además, parallelintercalará y desordenará las grepsalidas.
orion
1
Comprobaré que eso pvno causa el problema, gracias por el consejo.
Denis Bazhenov