运行 多个大文本文件上的多个管道命令的快速方法

Fast way to run several piped commands on many big text files

我有大量存储在文本文件中的数据(每个文件中有一天的数据,最大大小约为 1.5gb)。它们是数据馈送,因此必须将它们处理成人类可读的格式,这是由几个 C 程序(不是我写的)完成的。

我通过 f.ex 运行 命令

获得了一天的一些特定数据
decode.exe < ResourceTXT/itch-20140530.txt | select.exe -I 101 | bookgen.exe  -t -r | dump.exe > Output/20140530.txt

我创建了另一个简单的 C++ 命令行实用程序,它为我提供了其中的几个 "queries",用于给定的日期范围和选项

例如,

query 20140530 20140601 101 B -t -r

给我输出

decode.exe < ResourceTXT/itch-20140530.txt | select.exe -I 101 | bookgen.exe  -t -r | dump.exe > Output/20140530.txt
decode.exe < ResourceTXT/itch-20140531.txt | select.exe -I 101 | bookgen.exe  -t -r | dump.exe > Output/20140531.txt
decode.exe < ResourceTXT/itch-20140601.txt | select.exe -I 101 | bookgen.exe  -t -r | dump.exe > Output/20140601.txt

我可以通过管道传输到例如 run.bat。然后我可以 运行 这个 bat 文件,按顺序处理每个文本文件,然后用

将所有内容合并到一个文件中
copy /b *.txt my_data.txt

然后删除中间的.txt文件,我就完成了。

但是如果我需要生成 100 天的数据,这是一种非常慢的方法。

我知道我可以使用 start 命令启动 100 个 windows 命令实例。但是为了让它在这里工作,我需要创建 100 个 .bat 文件,每个文件包含 "query" 为 运行,然后创建另一个 final.bat 文件来启动所有这些 bat 文件,例如:

start batfile1.bat
start batfile2.bat
...
start batfile100.bat

然后运行final.bat.

这感觉像是一种麻烦且有点不雅的方式。由于我是新手,我只想确认这是否是处理我的问题的一种 OKAY 方法,或者我是否在做一些非常愚蠢的事情,或者忽略了任何重要的事情。谢谢。

注意:我正在帮助一个人解决这个问题,他想将所有内容保存在 Visual C++ 项目中(处理原始数据馈送的原始 C 程序 [decode.exe,等等]移植到这个项目)。所以这意味着一切都应该用 C++ 或 Windows 批处理文件来完成。

编辑: 这是 Aacini 请求的信息:

第一种方法:

Start: 16:01:12,62
End:   16:02:02,12

第二种方法:

Start decode:  16:03:32,05
Start select:  16:04:28,49
Start bookgen: 16:04:37,11
Start dump:    16:04:37,35
End:           16:04:38,04

哇,所以似乎最好只在每个文本文件上 运行 decode.exe,然后存储该二进制数据以备后用...? (但另一个问题是这些二进制编码文件实际上是 .txt 文件中原始数据大小的两倍...)

为了提高方法的效率,您可以测试几个点。

首先,通过管道连接的几个进程的效率取决于几个因素,但无论如何最终结果总是与最慢的进程相关联。这意味着如果我们确定最慢的进程并给它更多 CPU 时间,我们可能会提高总体效率。

您可以开始进行一些时序测试;例如,首先以这种方式测试您的原始方法:

echo Start: %time%
decode.exe < ResourceTXT/itch-20140530.txt | select.exe -I 101 | bookgen.exe  -t -r | dump.exe > Output/20140530.txt
echo End:   %time%

然后,将管道进程分成使用临时文件的单独进程:

echo Start decode:  %time%
decode.exe < ResourceTXT/itch-20140530.txt > temp1.txt
echo Start select:  %time%
select.exe -I 101 < temp1.txt > temp2.txt
echo Start bookgen: %time%
bookgen.exe  -t -r < temp2.txt > temp3.txt
echo Start dump:    %time%
dump.exe < temp3.txt > Output/20140530.txt
echo End:           %time%

如果此方法的总时间比前一种大很多,则说明计算机有几个CPU核,并行处理正常由 OS 同步。然而,这种方法的总时间可能与前一种方法相似甚至略小,这不仅是因为 CPU 有几个 CPU 核心,而且因为每个进程 运行 最大速度,无需 start/stop 同步来等待先前进程生成的数据。当然,第二种方法需要更大的磁盘 space 来存放临时文件,所以在这种情况下我们需要使用更多的内存来获得更少的时间(像往常一样)。

在第二种方法中,我们还可以确定每个单独进程所花费的时间,然后使用它们将可用的 CPU 内核分配到所有进程中。例如,我们可以使用每个 CPU 核心到 运行 多个最快的进程之一和最慢的进程之一;这个想法是浪费尽可能少的 CPU 处理时间。您可以使用 echo %NUMBER_OF_PROCESSORS%.

来确定核心数

编写一个批处理文件,用 100 个不同的日期重复 100 个进程相对简单,但我会等待以前的计时测试的结果,以便为您编写最佳解决方案。拜托,post 编辑原始问题的结果,并在这里给我留下评论作为建议。

编辑:第一版解决方案

这是解决方案的第一个版本。我按照您最初的建议使用了启动四个管道 .exe 程序的几个并行实例的方法,因为另一种方法太复杂了。这个程序的大部分应该看起来类似于您的查询 C++ 程序。有趣的部分是活动实例数的控制,但使用的方法很简单。有几种方法可以计算程序的活动实例数(如 for /F ... in ('tasklist ... ^| findstr ...') do ...),但我更喜欢只使用内部 Batch 命令,因为重复执行 tasklist.exe 和 findstr.exe 外部命令(除了 for /F 命令中使用的 cmd.exe 的一个额外副本,加上管道每一侧的一个额外副本)消耗太多 CPU 时间。我使用的方法,基于标志文件的存在,非常简单高效:在每个实例启动前创建一个标志文件,并在实例结束时删除。这样,要知道有多少实例处于活动状态,只需计算标志文件的数量即可。

理论上,当活动实例数等于CPU个核心数(您的情况下为8个)时,此方法应该达到最佳效率;但是,有几个因素可能会影响实际行为。虽然程序本身有可能修改活动实例的数量并计算效率如何变化,但这种管理所需的代码又大又复杂,所以我选择了一个更简单的解决方案。该程序允许手动设置并行实例的数量以及该方法在检查实例结束的周期中等待的秒数。如果第二个数字太小,循环将花费太多 CPU 时间;如果数字太大,该方法将在一个实例结束后等待太多时间才能开始下一个实例(浪费可用 CPU 时间)。

@echo off
setlocal EnableDelayedExpansion

if "%~4" neq "" goto begin

echo Usage: %0 numOfProcesses secondsToWait startDate endDate [options]
echo/
echo    numOfProcesses - Number of simultaneous queries to run in parallel
echo    secondsToWait  - Seconds to wait between process checking
echo    start/end Date - In YYYYMMDD format
echo    options        - First options are for select.exe, followed by
echo                     B opts for bookgen.exe, and D opts for dump.exe
echo/
echo For example:
echo    %0 8 10 20140530 20140601 -I 101 B -t -r
goto :EOF

:begin

set    "maxProcs=%1"  & shift
set /A "seconds=%1+1" & shift
set    "startDate=%1" & shift
set    "endDate=%1"

rem Get the options for each process
set "proc=S"
set "procs= B D "
:nextOpt
   shift
   if "%1" equ "" goto continue
   if "!procs: %1 =!" neq "%procs%" (
      set "proc=%1"
   ) else (
      set "%proc%_opts=!%proc%_opts! %1"
   )
goto nextOpt
:continue

rem Initialize date variables
set M=100
for %%a in (31 28 31 30 31 30 31 31 30 31 30 31) do (
   set /A M+=1
   set "daysPerMonth[!M!]=1%%a"
)
set /A Y=%startDate:~0,4%, M=1%startDate:~4,2%, D=1%startDate:~6,2%, leap=Y%%4
if %leap% equ 0 set "daysPerMonth[102]=129"

rem Start the initial set of N parallel processes
del query.log *.flg 2> NUL
set startTime=%time%
set /A query=0, active=0
:nextQuery
   set /A query+=1
   echo %query%- %Y%%M:~1%%D:~1% Started @ %time% >> query.log
   echo X > %Y%%M:~1%%D:~1%.flg
   start /B cmd.exe /D /C decode.exe ^< ResourceTXT/itch-%Y%%M:~1%%D:~1%.txt ^| select.exe %S_opts% ^| bookgen.exe %B_opts% ^| dump.exe %D_opts% ^> Output/%Y%%M:~1%%D:~1%.txt ^& del %Y%%M:~1%%D:~1%.flg
   ECHO Query %query%- %Y%%M:~1%%D:~1% started
   set /A D+=1
   if %D% gtr !daysPerMonth[%M%]! (
      set /A  D=101, M+=1
      if !M! gtr 112 (
         set /A M=101, Y+=1, leap=Y%%4
         if !leap! equ 0 set "daysPerMonth[102]=129"
      )
   )
   if %Y%%M:~1%%D:~1% gtr %endDate% goto waitEndQueries
   set /A active+=1
if %active% lss %maxProcs% goto nextQuery

ECHO/
ECHO Initial set of %maxProcs% queries started, there are pending queries

rem Cycle of: wait seconds, count active processes, start a new one
:waitQuery
   ECHO/
   ECHO Waiting for an active query to end, in order to start the next one
   ping -n %seconds% localhost > NUL
   set active=0
   for %%a in (*.flg) do set /A active+=1
   if %active% geq %maxProcs% goto waitQuery
   set /A query+=1
   echo %query%- %Y%%M:~1%%D:~1% Started @ %time% >> query.log
   echo X > %Y%%M:~1%%D:~1%.flg
   start /B cmd.exe /D /C decode.exe ^< ResourceTXT/itch-%Y%%M:~1%%D:~1%.txt ^| select.exe %S_opts% ^| bookgen.exe %B_opts% ^| dump.exe %D_opts% ^> Output/%Y%%M:~1%%D:~1%.txt ^& del %Y%%M:~1%%D:~1%.flg
   ECHO Query %query%- %Y%%M:~1%%D:~1% started
   set /A D+=1
   if %D% gtr !daysPerMonth[%M%]! (
      set /A D=101, M+=1
      if !M! gtr 112 (
         set /A M=101, Y+=1, leap=Y%%4
         if !leap! equ 0 set "daysPerMonth[102]=129"
      )
   )
if %Y%%M:~1%%D:~1% leq %endDate% goto waitQuery

echo/
echo All requested queries has been started

rem Wait for the rest of active processes to end
:waitEndQueries
ping -n %seconds% localhost > NUL
if exist *.flg goto waitEndQueries

rem Complete the whole process:
(
echo/
echo Queries from %startDate% to %endDate%
echo Total queries processed: %query%
echo Start time: %startTime%
echo End time:   %time%
) >> query.log

copy /b *.txt my_data.ok
del *.txt
ren my_data.ok my_data.txt

我建议你做一些测试处理文件大约 15-20 天。从 8 个活动实例和 1 秒等待开始,然后用多 1 个实例和少 1 个实例重复测试。如果其中一项更改导致总时间减少,请在同一方向用 1 个实例 more/less 重复测试。当您找到最佳实例数时,进行类似的测试,增加等待的秒数。之后,您可以以最快的方式处理您的 100 个或任意数量的文件。

如果您能 post 一些测试结果,我将不胜感激。如果您有任何问题或疑问,请给我留言。