оптимизация команды awk для большого файла - PullRequest
0 голосов
/ 25 августа 2018

У меня есть эти функции для обработки текстового файла объемом 2 ГБ.Я разделяю его на 6 частей для одновременной обработки, но это все еще занимает 4+ часа.

Что еще я могу попытаться сделать скрипт быстрее?

Немного подробностей:

  1. Я передаю свой входной csv в цикл while, который будет читаться построчноline.
  2. Я получил значения из строки csv из 4 полей функции read2col
  3. awk в моей функции mainf берет значения из read2col и выполняет некоторые арифметические вычисления.Я округляю результат до 2 десятичных знаков.Затем напечатайте строку в текстовом файле.

Пример данных:

"111","2018-08-24","01:21","ZZ","AAA","BBB","0","","","ZZ","ZZ111","ZZ110","2018-10-12","07:00","2018-10-12","08:05","2018-10-19","06:30","2018-10-19","09:35","ZZZZ","ZZZZ","A","B","146.00","222.26","76.26","EEE","abc","100.50","45.50","0","E","ESSENTIAL","ESSENTIAL","4","4","7","125","125"

Сценарий:

read2col()
{
is_one_way=$(echo "$line"| awk -F'","' '{print $7}')
price_outbound=$(echo "$line"| awk -F'","' '{print $30}')
price_exc=$(echo "$line"| awk -F'","' '{print $25}')
tax=$(echo "$line"| awk -F'","' '{print $27}')
price_inc=$(echo "$line"| awk -F'","' '{print $26}')
}


#################################################
#for each line in the csv
mainf()
{
cd $infarepath

while read -r line; do
        #read the value of csv fields into variables
        read2col

        if [[ $is_one_way == 0 ]]; then
                if [[ $price_outbound > 0 ]]; then
                        #calculate price inc and print the entire line to txt file
                        echo $line | awk -v CONVFMT='%.2f' -v pout=$price_outbound -v tax=$tax -F'","' 'BEGIN {OFS = FS} {$25=pout;$26=(pout+(tax / 2)); print}' >>"$csvsplitfile".tmp
                else
                        #divide price ecx and inc by 2 if price outbound is not greater than 0
                        echo $line | awk -v CONVFMT='%.2f' -v pexc=$price_exc -v pinc=$price_inc -F'","' 'BEGIN {OFS = FS} {$25=(pexc / 2);$26=(pinc /2); print}' >>"$csvsplitfile".tmp
                fi
        else
                echo $line >>"$csvsplitfile".tmp
        fi

done < $csvsplitfile
}

1 Ответ

0 голосов
/ 25 августа 2018

Первое, что нужно сделать 1002 *, это прекратить вызывать шесть субоболочек для запуска awk для каждой строки ввода. Давайте сделаем несколько быстрых расчетов за пределами конверта.

Если предположить, что ваши строки ввода имеют длину около 292 символов (как в вашем примере), файл 2G будет состоять из чуть более 7,3 миллиона строк. Это означает, что вы запускаете и останавливаете колоссальные сорок четыре миллиона процессов.

И, хотя Linux превосходно обрабатывает fork и exec настолько эффективно, насколько это возможно, это не без затрат:

pax$ time for i in {1..44000000} ; do true ; done
real 1m0.946s

Кроме того, bash на самом деле не был оптимизирован для такого рода обработки, его дизайн приводит к неоптимальному поведению для этого конкретного варианта использования. Подробнее об этом см. отличный ответ на одном из наших родственных сайтов.

Анализ двух методов обработки файлов (одна программа читает весь файл (каждая строка имеет только hello) и bash читает его по строке за раз) показана ниже. Две команды, используемые для получения времени были:

time ( cat somefile >/dev/null )
time ( while read -r x ; do echo $x >/dev/null ; done <somefile )

Для файлов разного размера (user+sys время, усредненное за несколько прогонов), это довольно интересно:

# of lines   cat-method   while-method
----------   ----------   ------------
     1,000       0.375s         0.031s
    10,000       0.391s         0.234s
   100,000       0.406s         1.994s
 1,000,000       0.391s        19.844s
10,000,000       0.375s       205.583s
44,000,000       0.453s       889.402s

Из этого следует, что метод while может сохранять свои собственные значения для небольших наборов данных, действительно плохо масштабируется.


Поскольку awk сам имеет способы выполнять вычисления и форматировать вывод, обрабатывая файл одним одиночным awk сценарием, а не * bash / multi- awk -per-line Это приведет к тому, что затраты на создание всех этих процессов и линейные задержки исчезнут.

Этот скрипт будет хорошей первой попыткой, назовем его prog.awk:

BEGIN {
    FMT = "%.2f"
    OFS = FS
}
{
    isOneWay=$7
    priceOutbound=$30
    priceExc=$25
    tax=$27
    priceInc=$26

    if (isOneWay == 0) {
        if (priceOutbound > 0) {
            $25 = sprintf(FMT, priceOutbound)
            $26 = sprintf(FMT, priceOutbound + tax / 2)
        } else {
            $25 = sprintf(FMT, priceExc / 2)
            $26 = sprintf(FMT, priceInc / 2)
        }
    }
    print
}

Вы просто запускаете сценарий single awk с:

awk -F'","' -f prog.awk data.txt

С данными испытаний, которые вы предоставили, вот до и после, с маркерами для полей с номерами 25 и 26:

                                                                                                                                                                                      <-25->   <-26->
"111","2018-08-24","01:21","ZZ","AAA","BBB","0","","","ZZ","ZZ111","ZZ110","2018-10-12","07:00","2018-10-12","08:05","2018-10-19","06:30","2018-10-19","09:35","ZZZZ","ZZZZ","A","B","146.00","222.26","76.26","EEE","abc","100.50","45.50","0","E","ESSENTIAL","ESSENTIAL","4","4","7","125","125"
"111","2018-08-24","01:21","ZZ","AAA","BBB","0","","","ZZ","ZZ111","ZZ110","2018-10-12","07:00","2018-10-12","08:05","2018-10-19","06:30","2018-10-19","09:35","ZZZZ","ZZZZ","A","B","100.50","138.63","76.26","EEE","abc","100.50","45.50","0","E","ESSENTIAL","ESSENTIAL","4","4","7","125","125"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...