Board logo

标题: [注册表类] 【已解决】有没有办法只通过代码来增加bat导出注册表的运行速度? [打印本页]

作者: aceamuro    时间: 2021-11-29 17:16     标题: 【已解决】有没有办法只通过代码来增加bat导出注册表的运行速度?

本帖最后由 aceamuro 于 2021-12-3 10:07 编辑

我用下面这个代码导出了300多条注册信息,大概花了4.5秒的时间(秒表计时)
  1. for /f %%i in (导出.txt) do (reg export %%i 导出.reg&&type 导出.reg>>备份.reg&&del 导出.reg)
复制代码
而把这条代码拆成两份同时运行大概只花了不到3秒(两个cmd窗口同时运行),说明通过一些安排还是可以提高代码运行效率的。
但我继续拆成4份却用了将近4秒,时间反而更长了
像提取注册表这种事,如果直接在注册表编辑器或用Total Uninstall保存的话瞬间就能完成,所以我感觉跟硬件大概没啥关系
请问能不能只靠代码更进一步提升上面那行命令(导出注册表项)的效率?
作者: Batcher    时间: 2021-11-29 22:29

回复 1# aceamuro


试试这样需要几秒:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set "n=1000"
  4. for /f %%i in (导出.txt) do (
  5.     set /a n+=1
  6.     reg export %%i 导出_!n!.reg
  7. )
  8. type 导出_*.reg >备份.reg
  9. del /f /q 导出_*.reg
复制代码

作者: aceamuro    时间: 2021-11-29 22:51

回复 2# Batcher
基本一样,拆成两份后时间差不多,不拆的话甚至平均慢了半秒,也可能是秒表计时的误差,但大致速度差不多
作者: wudi61600963    时间: 2021-11-30 07:47

本帖最后由 wudi61600963 于 2021-11-30 13:21 编辑

代码写得有问题看不明白具体需求:

· 为什么要从 导出.txt 中导出注册表到 导出.reg?
· 为什么要用 type 语法将 导出.reg 中内容复制到 备份.reg?
· 之后 导出.reg 要删除的话,为什么不直接导出至 备份.reg?
---------------------------------

想优化就要简化需求,如果是导出某项注册表的话:
  1. reg export 注册表项 备份.reg
复制代码
如果下面还有其他操作暂时用不到 备份.reg 的话可以“多线程”放到后台处理:
  1. start/B reg export 注册表项 备份.reg
复制代码
加速导出单个注册表大项可以考虑拆分并创建多个“多线程”来加速处理:
  1. for %%# in (注册表项1 注册表项2 ...) do start/B reg export %%# 备份_%%#.reg>nul
复制代码

作者: aceamuro    时间: 2021-11-30 09:27

回复 4# wudi61600963
1.因为项目太多,而且需要导出的项可能有变化的情况,所以单独有一个列表
2.因为reg export每次只能写入一条,写入下一条还是同名的“导出.reg”,bat会提示是否覆盖造成批处理中止
3.因为reg export只能把1条注册项写入1个文件,而列表中有300多条,所以没办法直接写入备份.reg。

或者有什么能一次导出多条注册表项的写法求告知……

我顶楼说拆成两份其实就是把这300多项注册单拆成两份,然后利用start命令同时运行另一个bat,不知道这个算不算“多线程”,这样拆成两份有效,拆得更碎的话速度反而会降低
我不太了解bat多线程的操作,如果有正规方法也求告知,实在没查到……
作者: wudi61600963    时间: 2021-11-30 17:38

本帖最后由 wudi61600963 于 2021-12-1 10:45 编辑

个人认为应该先了解下语法(以及执行效率),在语法可实现范围内整理思路找到最优解会比较好。
· 避免被覆盖可以从名称上着手,例如在文件名上加入 %random% 或 %time% ;
· 禁用覆盖提示添加 /y 参数(不用提示就强行覆盖现有文件);
· type 语法会按行读取文本内容,对于复制内容场景效率极低,同类办法有复制(copy)、移动(move)、重命名(rename),当然最高效的方案是不进行任何操作。


实现“多线程”的方法有很多,因为不清楚具体的应用场景和设备性能,以及有多追求速度,所以提供一些思路:
· 以最多线程并发导出(在大规模项目中会造成卡顿甚至宕机)
  1. @echo off
  2. pushd "%~dp0"
  3. setlocal enabledelayedexpansion
  4. for /F %%# in ('type 导出.txt') do start/B reg export %%# 导出_!random!.reg>nul
复制代码
· 添加控制降低并发数
  1. @echo off
  2. pushd "%~dp0"
  3. setlocal enabledelayedexpansion
  4. set "control0=start/B"
  5. for /F %%# in ('type 导出.txt') do set/an+=1&%control!n:~-1!% reg export %%# 导出_!random!.reg>nul
复制代码
或是可以先获取注册表项并存储在内存中(通过变量存储),待查询全部结束后将变量一齐写入等
作者: aceamuro    时间: 2021-11-30 18:46

本帖最后由 aceamuro 于 2021-11-30 18:52 编辑

回复 6# wudi61600963

还是那个问题,reg export只能导出一行或几行,导出一次后下次导出会把前面的结果覆盖掉。
试了几次,cmd窗口显示至少导出成功100多行,但结果的reg文件却只有三四行,有时候只有一行
先获取注册表项作为变量存储在内存中再统一写入看起来是好办法,但我基础不好写得有点懵……

PS:不是覆盖掉,刚刚把/b去掉看子窗口都是卡在提示“文件重名是否覆盖”,并没有实际写入文件,总之看起来还是reg export只能导出一条注册项到一个文件的问题……
作者: wudi61600963    时间: 2021-11-30 20:48

回复 7# aceamuro


    我修改了上面的代码,再次尝试。
作者: aceamuro    时间: 2021-12-1 08:26

回复 8# wudi61600963
这样虽然能够全部导出,但100多条注册表项都是一堆分散的reg文件(文件名是导出_随机数字),想要整合成一个的话,结果除了type我还是想不到别的
虽然说纯导出的速度的确是有些提高,但算上写入同一个文件的时间感觉上差不多(还没测试)
作者: wudi61600963    时间: 2021-12-1 09:21

本帖最后由 wudi61600963 于 2021-12-1 09:31 编辑

6 楼的代码是为提供思路而准备的范例(解决被覆盖问题),并非为楼主项目而量身定制的现成代码,楼主应该按照自己的实际需求进行修改寻求最优解,不应一点不改拿来用。

此外个人认为应该先了解下批处理语法,多使用搜索功能(关键字:“批处理 合并文件”),楼主现在的情况对基础语法不熟悉(例如 copy 合并文件的用法),很难继续写下去。
作者: aceamuro    时间: 2021-12-1 09:26

本帖最后由 aceamuro 于 2021-12-1 09:53 编辑

回复 10# wudi61600963
在后面分别加了
  1. copy 导出_*.reg 备份.reg
复制代码
  1. type 导出_*.reg>>备份.reg
复制代码
速度好像感觉差不多,可能100多个注册项还是体现不出速度差异,不过整体速度的确比原来有提升
实际测试后type和copy都比我想象得要快,感觉提速主要是因为把每一项导出都用start/b另开进程了,如果实机的话提速应该会明显得多(我用虚拟机测试的)

不过我还是觉得你上面提的那个先用变量导出到内存,然后一股脑写入一个文件的办法会更快一些,但现在还没啥头绪……
作者: wudi61600963    时间: 2021-12-1 10:22

本帖最后由 wudi61600963 于 2021-12-1 10:23 编辑

回复 11# aceamuro


    优化效率来说越底层,需要操作的内容越少越单纯效率越高。
    type 语法按行读取,copy 则按文件,因此 copy 的效率会更高,具体差距可以写一个计时脚本来验证,在大项目中差距明显。

    多线程操作可以增加效率,但自身也会引入一些新的隐患,例如楼主项目中出现某些意外情况新的"线程"的文件还没处理完,主线程就开始执行 copy 操作,就会造成合并的文件不完整,因此对于时间的把握要恰到好处,最好有一些冗余或者增加一个验证。
    此外,批处理没有类似于其他语言(例如C、.net 等)的操作底层语法,追求更高效的话加入 powershell 也是不错的选择。
作者: Batcher    时间: 2021-12-1 10:35

回复 11# aceamuro


    “先用变量导出到内存”不适合你这个需求,可以先跳过这个思路。
作者: aceamuro    时间: 2021-12-1 12:39

回复 13# Batcher
了解了
作者: aceamuro    时间: 2021-12-1 17:28

本帖最后由 aceamuro 于 2021-12-1 18:01 编辑

回复 12# wudi61600963
我想让输出的文件按照序号来排列,这样当出现特定序号的文件时就可以作为导出完毕的验证
  1. setlocal enabledelayedexpansion
  2. for /F %%# in ('type 导出.txt') do (
  3. set /a n+=1
  4. start/b reg export %%# 导出_!n!.reg)
复制代码
但这样写的话,如果遇到不存在的键值就不会生成文件,不一定会得到准确的值
于是我想先判断是否存在键值,如果存在就导出,不存在就生成一个空文件,这样只要“导出.txt”这个文件的条目不变,最后就一定会生成固定序号的文件
代码就改成这样
  1. setlocal enabledelayedexpansion
  2. for /F %%# in ('type 导出.txt') do (
  3. set /a n+=1
  4. REG QUERY "%%#" /s  >nul 2>nul
  5. if %errorlevel%==0 (start/b reg export %%# 导出_!n!.reg) else (echo 0>>导出_!n!.reg))
复制代码
但结果不存在键值的项还是没法生成空文件,求大佬修正
好像是因为不管引用项存不存在,%errorlevel%都会被取值为0,实在检查不出错在哪……
作者: qixiaobin0715    时间: 2021-12-1 18:31

变量延迟用!errorlevel!
作者: aceamuro    时间: 2021-12-1 18:46

回复 16# qixiaobin0715
基础不好忘记了,感谢提醒,改过来后就正常了
作者: Batcher    时间: 2021-12-1 23:32

回复 15# aceamuro


2楼代码的本意是想把你顶楼代码for循环内部的文件写入操作挪到for循环外,通常来说这样可以提升执行速度,参考:
https://mp.weixin.qq.com/s/VZk0TmYUpFdCoWK9ZpgL0Q

不过遗憾的是,你3楼描述的结果未能发现明显效果。

start /b 的问题在于【当出现特定序号的文件时就可以作为导出完毕的验证】这个逻辑在理论上不一定成立,因为:
假设文本总共300行,第300行完成reg export的时候,第299行不一定是否已经执行完成。
作者: aceamuro    时间: 2021-12-2 09:19

本帖最后由 aceamuro 于 2021-12-2 09:21 编辑

回复 18# Batcher
用代码测了一下精确到毫秒的时间
导出.txt总共209条注册项,如果有几十条真实存在于注册表中,2L跟1L代码的用时基本没什么区别,如果209条全部都有的话2L代码的确有500毫秒左右的领先,昨天应该是秒表计时的误差
不过终究还是start/b的速度快得多,209个注册项全部存在的话比2L代码快1500毫秒左右,基本是一半的时间
大佬提示的那个逻辑我的确想得不够深,请问如果大部分用start/b,最后10来条用正常的reg export会不会更保险一些?
  1. setlocal enabledelayedexpansion
  2. for /F %%# in ('type 导出.txt') do (
  3. set /a n+=1
  4. if !n! lss 200 (start/b reg export %%# 导出_!n!.reg) else (reg export %%# 导出_!n!.reg)
  5. )
复制代码
这样是不是也不用管编号是否到209,直接写copy命令就行?
作者: Batcher    时间: 2021-12-2 09:32

回复 19# aceamuro


    请教一下,你是如何判断最后10来条正常?
作者: aceamuro    时间: 2021-12-2 09:39

本帖最后由 aceamuro 于 2021-12-2 09:47 编辑

回复 20# Batcher
我也不会判断,就是直接写reg export,把前面的start/b去掉而已,大佬的意思是说这样写无法确定是否正常的么?
我的意思是前面199条用start/b分开其他线程写入,最后10条用主线程写入
主线程写10条的时间总够其他线程写1条了吧,所以感觉上好像保险一些?不知道这样好不好使,写法对不对……
作者: wudi61600963    时间: 2021-12-2 10:58

本帖最后由 wudi61600963 于 2021-12-2 11:09 编辑

大概率不会出什么问题,但正如之前所说,出现了意外情况(I/O 阻塞、单项注册表过大或是其他原因)则会最终造成不完整合并,因此属于存在隐患的“非可靠设计”。

增加可靠性的方法也有很多,例如当全部执行完成后对文件/文件大小/文件数量进行验证,或者是对文件是否正被使用进行验证(正在写入的文件无法通过验证)、收集导出日志/错误日志来核对等等。

从结构上也可以参考面向对象语言的封装结构进行设计。
使用 || 语法可以判断语句是否执行成功,但使用 start/B 会改变执行判断标准,使其成为是否成功执行了 start/B 语句。因此可以设计一个封装模块,实时报告进度,或是无论成功失败都创建一个信号,那么根据信号就可以掌握执行结果了。

当然并非“非可靠设计”就一定不行,找到执行边界(例如单项注册表最大可以多大、执行环境中最多耗时多久等),优化判断和超时时限令边界情况几乎不可复现也不失为一种选择。
作者: aceamuro    时间: 2021-12-2 12:39

本帖最后由 aceamuro 于 2021-12-2 14:52 编辑

回复 22# wudi61600963
对我来说太深奥了,我代码很菜,这些基本不会写啊……
不过大佬提到文件是否正在被使用的问题,我又想到一个办法,那就是把导出结果移动到另一个文件夹,如果能移动说明导出完成了,不能移动说明文件正在被写入,那就重新移动直到移空,能移空是不是就表示导出全部完成了?
  1. setlocal enabledelayedexpansion
  2. for /F %%# in ('type 导出.txt') do (start/b reg export %%# a\导出_!random!.reg)
  3. :进度验证
  4. move a\*.* b\
  5. dir /a /s /b a | findstr . >nul && goto 进度验证 || copy b\导出_*.reg 备份.reg
复制代码
思路就是这样,不知道上面写得对不对,如果对的话是不是就相当于完整验证,没有隐患了?


补充1:这样写看来不行,偶尔会有漏掉一两个的情况,但奇怪的是漏掉的文件既不在a也b文件夹,就好像没有导出过
但用最保守的方法导出数量却总是一样的

补充2:好像是随机文件名偶尔会造成文件名冲突,把随机改成序号就没再出问题了,每次导出的数量都一样,而且速度也跟直接用start/b差不多
作者: wudi61600963    时间: 2021-12-3 16:06

本帖最后由 wudi61600963 于 2021-12-3 16:20 编辑

回复 23# aceamuro

花时间仔细琢磨一下上面写的内容,查资料、对比不同方案、做减法、反复进行耗时测试会很容易找到更高效的解。
因此现阶段单纯改代码意义不大,代码会随着思想成长、方案优化而变化。
当楼主靠自身能力无法继续优化时拿出来大家一同修改会更高效。
作者: cmd1152    时间: 2021-12-3 17:30

说真的,关掉杀毒软件会快很多。
没有关的速度:8.627秒
关掉的速度:1.003秒




欢迎光临 批处理之家 (http://www.bathome.net/) Powered by Discuz! 7.2