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

时间:2015-04-10 11:57:14

标签: batch-file visual-c++ parallel-processing

我有大量的数据存储在文本文件中(每个文件中有一天的数据,最大大小约为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 ++命令行实用程序,它为给定的日期范围和选项提供了几个“查询”

例如,

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文件,每个文件包含要运行的“查询”,然后创建另一个final.bat文件,启动所有这些bat文件,如:

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

然后运行 final.bat

这感觉就像一个繁琐而且有点不雅的方式。由于我是新手,我只想确认这是否是一种解决我的问题的好方法,或者我是做一些非常愚蠢的事情,还是忽略了任何重要的事情。谢谢。

注意:我正在帮助这个人,他希望将所有内容保存在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文件中原始数据的大小......)

1 个答案:

答案 0 :(得分:2)

为了提高方法的效率,您可以测试几点。<​​/ p>

首先,通过管道连接的几个过程的效率取决于几个因素,但无论如何,最终结果始终与最慢的过程相关联。这意味着如果我们确定最慢的进程并为其提供更多的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核心,并且操作系统正确同步并行进程。但是,此方法的总时间可能与前一个方法相似甚至略小,不仅因为CPU具有少量CPU内核,而且因为每个进程以其最大速度运行而无需等待的启动/停止同步用于前一过程生成的数据。当然,第二种方法需要更大的磁盘空间用于临时文件,因此在这种情况下我们需要使用更多内存来获得更少的时间(像往常一样)。

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

写一个批处理文件,用100个不同的日期重复100个进程是相对简单的,但我会等待以前的计时测试结果,所以我可以为你编写最好的解决方案。请发布结果编辑原始问题,并在此留下评论作为建议。

编辑:解决方案的第一个版本

这是该解决方案的第一个版本。我使用了启动你最初建议的四个管道.exe程序的几个并行实例的方法,因为另一种方法太复杂了。该程序的大多数部分应该与您的查询C ++程序类似。有趣的部分是对活动实例数量的控制,但使用的方法很简单。有几种方法可以计算程序的活动实例数(如for /F ... in ('tasklist ... ^| findstr ...') do ...),但我更喜欢使用内部批处理命令,因为重复执行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个实例重复测试。如果其中一个更改导致总时间减少,则在同一方向上以更多/更少的实例重复测试。当您找到最佳实例数时,请执行类似的测试,增加等待的秒数。之后,您可以以最快的方式处理100个或任意数量的文件。

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