Mejorar el rendimiento de comparar la columna de archivo a array en bash

Estoy comparando filas en una serie de archivos contra un array de flotadores en bash. Brevemente, los archivos en cuestión (../summarize_eigenvectors/"$xct"_/-5_1-sorted.txt) tienen la siguiente estructura

    c    v    weight        ik        kx        ky        kz
    1    1   0.00000         1   0.00000   0.00000   0.00000
    1    1   0.00000         2   0.00000   0.04167   0.00000
    1    1   0.00000         3   0.00000   0.08333   0.00000
  

y el array se genera a partir de ../vici_absdipole_noeh/v1c5.data, que tiene el siguiente formato:

kx      ky      kz          ik    ic    iv    is    ec (eV)         ev (eV)        eig (eV)   abs(dipole)^2      Re(dipole)      Im(dipole)
0.00000 0.00044 0.00000      1     1     1     1  0.11713703E+01 -0.12426462E+01  0.24140165E+01  0.69913425E-04  0.81359347E-02  0.19287282E-02
0.00000 0.01883 0.00000      2     1     1     1  0.11760590E+01 -0.12490846E+01  0.24251436E+01  0.59405512E-04 -0.70114501E-03  0.76755396E-02
0.00000 0.03722 0.00000      3     1     1     1  0.11746489E+01 -0.12612625E+01  0.24359113E+01  0.37648401E-04 -0.46637404E-02  0.39872204E-02
0.00000 0.05561 0.00000      4     1     1     1  0.11868220E+01 -0.12787400E+01  0.24655620E+01  0.18552618E-04 -0.21585915E-02  0.37273450E-02

Lo que mi código hace es comparar los enteros en la cuarta columna del archivo v1c5.da contra mi matriz; si para una fila del archivo, el elemento de la cuarta columna está en el array "list", entonces los valores del índice de archivo, peso, ik, kx y ky en esa fila se hacen eco.

Aquí hay una muestra de mi código de trabajo, que compara 7485 archivos que tienen 1152 líneas cada uno a un conjunto de 1152 elementos

#!/bin/bash

#generates the array from information given in another file
for i in range $(seq 2 1153)
do
    ik=$(awk -v i=$i 'NR==i''{ print$4 }'  ../vici_absdipole_noeh/v1c5.data)
    dp2=$(awk -v i=$i 'NR==i''{ print$11 }'  ../vici_absdipole_noeh/v1c5.data)
    dp2f=$(printf "%.8f" $dp2)
    if (( $(echo "$dp2f > 6" |bc -l) )); then
        list+=("$ik" )
    fi
done

echo "xct   ik  weight  kx  ky" > v1c5-high_dp2_kpts.txt

task(){
    echo working on $xct
    for line in {1..1152};do
        weight=$(awk -v line=$line 'NR==line''{ print$3 }'  ../summarize_eigenvectors/"$xct"_*/*-5_1-sorted.txt)
        ik=$(awk -v line=$line 'NR==line''{ print$4 }'  ../summarize_eigenvectors/"$xct"_*/*-5_1-sorted.txt)
        kx=$(awk -v line=$line 'NR==line''{ print$5 }'  ../summarize_eigenvectors/"$xct"_*/*-5_1-sorted.txt)
        ky=$(awk -v line=$line 'NR==line''{ print$6 }'  ../summarize_eigenvectors/"$xct"_*/*-5_1-sorted.txt)
        if [[ " ${list[@]} " =~ " ${ik} " ]]; then
            echo "$xct  $ik $weight $kx $ky" >> v1c5-high_dp2_kpts.txt
        fi
    done
}

for xct in {1..7485};do
((i=i%360)); ((i++==0)) && wait
task "$xct" &
done
wait

El código ha funcionado durante 8 horas más y sólo ha procesado 1700 archivos, que es bastante lento. ¿Hay algún obstáculo en este código que limite su rendimiento? Y si lo hay, ¿cómo puedo mejorarlo?

Estoy ejecutando esto en el nodo del centro de computación de alto rendimiento que tiene 24 núcleos por nodo, por lo que he utilizado la paralelización también para acelerar las cosas. Aparentemente esto todavía no es suficiente.

Pregunta hecha hace 3 años, 4 meses, 27 días - Por algoarchitect


3 Respuestas:

  • Tu task la función es realmente ineficiente. Rereads all the files 4 times each time through the loop, just to process one line. Puedes hacer todo el trabajo en uno awk invocación.

    task(){
        echo working on $xct
        cat ../summarize_eigenvectors/"$xct"_*/*-5_1-sorted.txt |
            awk -v list="${list[*]}" -v xct="$xct" '
                BEGIN {
                    split(list, list_array); # split string list into array list_array at whitespace delimiters
                    for (i in list_array) list_hash[list_array[i]] = 1 # associative array with $list elements as keys
                }
                {
                weight=$3; ik=$4; kx=$5; ky=$6;
                if (ik in list_hash) printf("%s  %s %s %s %s\n", xct, ik, weight, kx, ky)
                }' >> v1c5-high_dp2_kpts.txt
    }
    

    Respondida el Dec 18, 2020 a las 02:47 - por hackharbinger

    Votos positivos: 0 | Votos negativos: 0

  • Para mejorar el rendimiento de tu script en bash, te sugiero algunas optimizaciones que puedes aplicar:

    • Reducir la cantidad de llamadas a `awk`: En tu script actual, estás llamando a `awk` varias veces dentro de los bucles para leer diferentes columnas en los archivos de datos. Esto puede ser ineficiente. Una alternativa más eficiente sería leer cada línea completa una vez en lugar de leer columnas separadas en múltiples llamadas a `awk`.
    • Optimizar la creación del array `list`: En lugar de recorrer todo el archivo `v1c5.data` para crear el array `list`, puedes utilizar un enfoque más eficiente. Por ejemplo, podrías utilizar `awk` para filtrar las líneas que cumplen con tu condición directamente al principio y luego extraer el valor de la cuarta columna.
    • Paralelización eficiente: Aunque estás utilizando la paralelización en tu script, es importante asegurarse de que estás distribuyendo adecuadamente la carga entre los núcleos disponibles. En tu caso, tienes 24 núcleos disponibles, por lo que podrías ajustar la cantidad de tareas que se están ejecutando en paralelo para aprovechar mejor los recursos.

    Aquí tienes un ejemplo de cómo podrías optimizar tu script:

    bash
    !/bin/bash
    

    Obtener los valores de ik que cumplen con el criterio en v1c5.data

    list=($(awk '$11 > 6 {print $4}' ../vici_absdipole_noeh/v1c5.data)) echo "xct ik weight kx ky" > v1c5-high_dp2_kpts.txt

    Iterar sobre cada archivo en paralelo para mejorar rendimiento

    parallel_process() { xct=$1 echo "working on $xct" for line in {1..1152}; do read -r weight ik kx ky <<< $(awk -v line=$line 'NR==line {print $3, $4, $5, $6}' ../summarize_eigenvectors/"$xct"_/-5_1-sorted.txt) if [[ " ${list[@]} " =~ " ${ik} " ]]; then echo "$xct $ik $weight $kx $ky" >> v1c5-high_dp2_kpts.txt fi done }

    Establecer el número de tareas en paralelo

    export -f parallel_process export list export -f parallel_process

    Iniciar procesos en paralelo

    for xct in {1..7485}; do ((i=i%24)); ((i++==0)) && wait parallel_process "$xct" & done wait

    Este código realiza algunas optimizaciones, como reducir la cantidad de llamadas a awk, paralelizar el proceso de manera más eficiente y manejar los valores de ik directamente en el script en lugar de crear un array. Prueba este código para ver si mejora el rendimiento de tu script.

    Respondida el Dec 19, 2020 a las 02:49 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • Optimizaciones de rendimiento:

    Paralelización: Si bien has utilizado la paralelización, podrías considerar aumentar el número de subprocesos utilizados para mejorar aún más el rendimiento.

    Almacenamiento de la matriz en memoria: En lugar de buscar en la matriz cada vez, puedes cargarla en la memoria una vez al inicio del programa. Esto eliminaría la sobrecarga de la búsqueda de archivos en cada iteración. Uso de herramientas nativas: En lugar de utilizar bc para las comparaciones de coma flotante, puedes utilizar las herramientas nativas de bash para comparar números de coma flotante. Uso de arrays asociativos: En bash, los arrays asociativos son más eficientes que los arrays normales para buscar elementos. Podrías considerar utilizar un array asociativo para almacenar los elementos de la matriz.

    Código optimizado:

    bash
    !/bin/bash
    

    Generates the array from information given in another file

    declare -A dp_array for i in $(seq 2 1153); do ik=$(awk -v i=$i 'NR==i' '{ print$4 }' ../vici_absdipole_noeh/v1c5.data) dp2=$(awk -v i=$i 'NR==i' '{ print$11 }' ../vici_absdipole_noeh/v1c5.data) dp2f=$(printf "%.8f" $dp2) if (( $(echo "$dp2f > 6" |bc -l) )); then dp_array["$ik"]="" fi done echo "xct ik weight kx ky" > v1c5-high_dp2_kpts.txt task() { local xct=$1 echo "Working on $xct" for line in {1..1152}; do weight=$(awk -v line=$line 'NR==line' '{ print$3 }' ../summarize_eigenvectors/"$xct"_/-5_1-sorted.txt) ik=$(awk -v line=$line 'NR==line' '{ print$4 }' ../summarize_eigenvectors/"$xct"_/-5_1-sorted.txt) kx=$(awk -v line=$line 'NR==line' '{ print$5 }' ../summarize_eigenvectors/"$xct"_/-5_1-sorted.txt) ky=$(awk -v line=$line 'NR==line' '{ print$6 }' ../summarize_eigenvectors/"$xct"_/-5_1-sorted.txt) if [[ -n "${dp_array[$ik]}" ]]; then echo "$xct $ik $weight $kx $ky" >> v1c5-high_dp2_kpts.txt fi done } export -f task parallel --max-procs 360 task ::: {1..7485}

    Explicación del código optimizado:

    Se utiliza un array asociativo (dp_array) para almacenar los elementos de la matriz. La función task se exporta y luego se ejecuta en paralelo mediante la utilidad parallel con 360 subprocesos. * Se utiliza un sistema de archivos local para almacenar archivos temporales. Esto evita la sobrecarga de lectura y escritura en el sistema de archivos de red.

    Respondida el Dec 19, 2020 a las 02:58 - por Gemini

    Votos positivos: 0 | Votos negativos: 0