Медленная обработка цикла for, который использует findstr - PullRequest
2 голосов
/ 28 ноября 2011

У меня есть несколько странный случай, когда цикл for невероятно медленный, когда я использую findstr в качестве строки для DO.

Стоит отметить, что файл (old-file.xml), который я 'Обработка m содержит около 200 000 строк.

Эта часть работает быстро, но может быть визуализирована медленнее, если я удаляю | find /c ":"

rem find total number of lines in xml-file
findstr /n ^^ old-file.xml | find /c ":" > "temp-count.txt"
set /p lines=< "temp-count.txt"

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

setlocal DisableDelayedExpansion
rem start replacing wrong dates with correct date
for /f "usebackq Tokens=1* Delims=:" %%i in (`"findstr /n ^^ old-file.xml"`) do (
    rem cache the value of each line in a variable
    set read-line=%%j
    set line=%%i
    rem restore delayed expansion
    setlocal EnableDelayedExpansion
    rem write progress in title bar
    title Processing line: !line!/%lines%
    rem remove trailing line number
    rem set read-line=!read-line:*:=!
    for /f "usebackq" %%i in ("%tmpfile%") do (
        rem replace all wrong dates with correct dates
        set read-line=!read-line:%%i=%correctdate%!
    )
    rem write results to new file
    echo(!read-line!>>"Updated-file.xml"
    rem end local
    endlocal
)

РЕДАКТИРОВАТЬ:

Дальнейшие исследования показали, что использование этой единственной строки, которая должна отображать текущий номер строки, занимает около 10 минут в моем 8-мегабайтном файле из 200 000 строк.Это просто для того, чтобы заставить его начать отображать строки.

for /f "usebackq Tokens=1* Delims=:" %%i in (`"findstr /n ^^ old-file.xml"`) do echo %%i

Так что, похоже, findstr пишет экранный вывод, скрытый для пользователя, но видимый для for -loop.Как я могу предотвратить это, все еще получая те же результаты?

РЕДАКТИРОВАТЬ 2: Решение

Решение, предложенное Aacini и, наконец,исправлено мной.

Это фрагмент из гораздо большего сценария.Неправильные даты извлекаются в другом цикле.И общее количество строк также извлекается из другого цикла.

setlocal enabledelayedexpansion
rem this part is for snippet only, dates are generated from another loop in final script 
echo 2069-04-29 > dates-tmp.txt
echo 2069-04-30 >> dates-tmp.txt

findstr /n ^^ Super-Large-File.xml > out.tmp

set tmpfile=dates-tmp.txt
set correctdate=2011-11-25
set wrong-dates=
rem hardcoded total number of lines
set lines=186442
for /F %%i in (%tmpfile%) do (
    set wrong-dates=!wrong-dates! %%i
)
rem process each line in out.tmp and loop them through :ProcessLines
call :ProcessLines < out.tmp
rem when finished with above call for each line in out.tmp, goto exit
goto ProcessLinesEnd
:ProcessLines
for /L %%l in (1,1,%lines%) do (
    set /P read-line=
    rem write progress in title bar
    title Processing line: %%l/%lines%
    for %%i in (%wrong-dates%) do (
        rem replace all wrong dates with correct dates
        set read-line=!read-line:%%i=%correctdate%!
    )
    rem write results to new file
    echo(!read-line:*:=!>>"out2.tmp"
)
rem end here and continue below
goto :eof

:ProcessLinesEnd
echo this should not be printed until call has ended

:exit
exit /b

Ответы [ 2 ]

4 голосов
/ 28 ноября 2011

Здесь две точки:

1- Команда setlocal EnableDelayedExpansion выполняется для каждой строки файла .Это означает, что примерно 200000 раз вся среда должна быть скопирована в новую область локальной памяти.Это может вызвать несколько проблем.

2- Я предлагаю вам начать с самой основной части.Сколько времени занимает выполнение findstr?Запустите findstr /n ^^ old-file.xml в одиночку и проверьте это, прежде чем пытаться исправить любую другую часть.Если этот процесс быстрый, добавьте к нему один шаг и тестируйте снова, пока не найдете причину замедления.Я предлагаю вам не использовать каналы или for /f для выполнения findstr, а для файла, созданного предыдущим перенаправлением.

РЕДАКТИРОВАТЬ Более быстрое решение

Есть еще один способ сделать это.Вы можете направить вывод findstr в подпрограмму Batch, чтобы строки можно было прочитать с помощью команды SET /P.Этот метод позволяет полностью обрабатывать строки с помощью отложенных расширений, а не с помощью командной строки FOR /F, поэтому пара команд setlocal EnableDelayedExpansion и endlocal больше не требуется.Однако, если вы по-прежнему хотите отобразить номер строки, необходимо вычислить его снова.

Кроме того, быстрее загружать неправильные даты в переменную, а не обрабатывать% tmpfile% с каждой строкойбольшой файл.

setlocal EnableDelayedExpansion
rem load wrong dates from tmpfile
set wrong-dates=
for /F %%i in (%tmpfile%) do (
    set wrong-dates=!wrong-dates! %%i
)
echo creating findstr output, please wait...
findstr /n ^^ old-file.xml > findstr.txt
echo :EOF>> findstr.txt
rem start replacing wrong dates with correct date
call :ProcessLines < findstr.txt
goto :eof

.

:ProcessLines
set line=0
:read-next-line
set /P read-line=
rem check if the input file ends
if !read-line! == :EOF goto :eof
rem write progress in title bar
set /A line+=1
title Processing line: %line%/%lines%
for %%i in (%wrong-dates%) do (
    rem replace all wrong dates with correct dates
    set read-line=!read-line:%%i=%correctdate%!
)
rem write results to new file
echo(!read-line:*:=!>>"Updated-file.xml"
rem go back for next line
goto read-next-line

ВТОРОЕ РЕДАКТИРОВАНИЕ Еще более быстрая модификация

Предыдущий метод может бытьнезначительное ускорение, если цикл выполняется с помощью команды for /L вместо goto.

:ProcessLines
for /L %%l in (1,1,%lines%) do (
    set /P read-line=
    rem write progress in title bar
    title Processing line: %%l/%lines%
    for %%i in (%wrong-dates%) do (
        rem replace all wrong dates with correct dates
        set read-line=!read-line:%%i=%correctdate%!
    )
    rem write results to new file
    echo(!read-line:*:=!>>"Updated-file.xml"
)

В этой модификации также пропущено сравнение EOF и вычисление номера строки, поэтому выигрыш во времени можетбыть значимым после того, как повторил это 200000 раз.Если вы используете этот метод, не забудьте удалить строку echo :EOF>> findstr.txt в первой части.

3 голосов
/ 28 ноября 2011

Выражение FOR / F всегда будет выполняться / считываться / оцениваться завершенным до запуска внутреннего цикла.

Вы можете попробовать это с

(
  echo line1
  echo line2
) > myFile.txt
FOR /F "delims=" %%a in (myFile.txt) DO (
  echo %%a
  del myFile.txt 2> nul >nul
)

Будет отображаться

line1
line2

В вашем случае все ('"findstr /n ^^ old-file.xml"') будет выполнено и кэшировано до того, как цикл может начаться

РЕДАКТИРОВАТЬ: Добавлено решение

Я измерял с файлом ~ 20 МБ с 370 000 строк

type testFile.txt > nul
findstr /n ^^ testFile.txt > nul

for /F "delims=" %%a in (testFile.txt) do ( 
  rem Nothing 
)

for /f "usebackq delims=" %%a in (`"findstr /n ^^ testFile.txt"`) do ...

findstr /n ^^ testFile.txt > out.tmp

type_nul     ~10000ms
findstr_nul  ~30000ms
for_file     ~ 1600ms
for_findstr  cancled after 10 minutes
findstr_tmp  ~  500ms !!!

Я бы рекомендовал использовать временный файл, он очень быстрый.

findstr /n ^^ myFile.txt > out.tmp
set lineNr=0
(
  for /f "usebackq delims=" %%a in ("out.tmp") do (
    set /a lineNr+=1
    set "num_line=%%a"
    setlocal EnableDelayedExpansion
    set "line=!num_line:*:=!"
    echo(!line!
    endlocal
  )
) > out2.tmp

Btw. Расщепление for / F может потерпеть неудачу, если исходная строка начинается с двоеточия
for /f "usebackq Tokens=1* Delims=:"

Образец: :ThisIsALabel
:ThisIsALabel
Findstr / n добавляет номер строки
17::ThisIsALabel
delims=: будет разделять первый токен и обрабатывать все двоеточия как один разделитель
ThisIsALabel

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...