一些示例场景:短文本,较少的换行符;短文,许多新线;中/长文本,许多换行符(代码);中/长文本,较少的换行符(文章).
我有一些不确定因素:如果我一个接一个地运行,第二个测试会被第一个测试污染吗?对于不同的场景(优化)需要不同的功能而不是一个功能适合所有(便利)需要多少差异?我需要BOTH Rebol 2.7.8和Rebol 3的基准测试.
以下是我想要测试的特定功能集,尽管还有更多通用答案:
reduce [ "@rebolek" func [text [string!] /local i][ parse text [(i: 1) any [thru newline (++ i)]] i ] "@darius" func [text [string!] /local tmp][ tmp: sort join text "^/" 1 + subtract index? find/last tmp "^/" index? find tmp "^/" ] "@endo64" func [text [string!]][ (length? text) - (length? remove-each v text [v = #"^/"]) ] "@BrianH" function [text [string!]] [ i: 1 find-all text newline [++ i] i ] "@MaxV" func [text [string!]][ write %.mytext text text: read/lines %.mytext delete %.mytext length? text ] ]REBOL(R2& R3)中的分析挑战是三倍.时间,循环和内存使用.
定时:
在某些操作系统上,默认时间不准确(如windows).这可以通过创建更大的循环来大大减轻,这些循环基本上将测试扩展到可接受的时序余量.你也可以创建更好的计时器,比如我为windows(chrono-lib.r)构建的计时器.
它还包括一个时间推移功能,使测试块很容易(基本上是一个更精确的’DT for windows).
循环:
这是一个显而易见的事情,当你计时,删除一些操作系统/多任务开销,你运行代码几次并将重复分开得到一个平均值.这通常足以获得一个好主意.
但是,在rebol中,循环本身就相当昂贵.迭代函数有自己的,不可忽略的开销.因此,在循环某些代码之前,请确保您使用的循环是最佳的,尤其要确保使用本机迭代器函数,因为循环最终可能比您尝试分析的代码慢.在R2中,FOREACH和LOOP在大多数情况下是更快的循环. FOREACH在开始循环之前有一点点绑定开销,但在我的大量测试中,这会产生不可估量的影响,因为它只发生一次,并且在几秒钟的运行中无关紧要.
在R3中,迭代器会在给定速度提升的情况下启动,因为R2中的某些mezz循环已经变为原生,因此您必须检查它们才能看到.
记忆:
这是事情变得不那么可预测的地方. REBOLs GC在分析时非常具有侵入性.它不仅速度慢,而且不可预测,无法实际优化,并且随着REBOL内存占用量的增加而减慢(事实上,比人们意识到的要多得多).
多少钱?让我们来看看:
这是我必须对内存使用和执行速度进行基准测试的脚本.它生成一个图形,可以显示一个与另一个的关系.它有一个关闭垃圾收集器的选项……你会看到它如何影响分析…有和没有的图形很有说服力:
rebol [ title: "profiling tests with graphics" author: "Maxim Olivier-Adlhoch" purpose: "given a few metrics and code blocks, will plot out the time and memory use for each repetition." ] ;---- ; test metrics loops: 2000 repetitions: 500 ;---- ; test options turn-off-garbage-collector?: false ;---- ; output gfx prefs mem-color: red time-color: sky bg-color: black gfx-size: 600x400 margins: 100x100 lw: 2 ; line-width label-color: white * .85 border-color: gray * 0.5 slices: 10 ; how many labels on the edges of the graphics. ;---- ; globals plot-data: [] ;---- ;- functions platform: does [ select [ 1 AMIGA 2 OSX 3 WIN32 4 LINUX ] system/version/4 ] ;---- ; make sure timer resolution is decent on all platforms ;---- either ( platform = 'WIN32 ) [ either exists? %libs/windows-chrono.r [ do %libs/windows-chrono.r ][ ask "download and save windows-chrono from rebol.org ? ^/^/ * press ENTER to confirm^/ * press ESCAPE to halt." make-dir %libs/ write %libs/windows-chrono.r read http://www.rebol.org/download-a-script.r?script-name=windows-chrono.r print "windows chrono downloaded" ] ][ ; on other platforms, the OS timer resolution tends to be better, we can just use delta-time time-lapse: :delta-time ] ; time-lapse: :delta-time ;---- ; TEST CODE INITIALISATIONS blk: make block! repetitions print "====================" print "running tests" ;---- ; SETUP TEST OPTION(S) if turn-off-garbage-collector? [ recycle/off ] ;-------------------------------------------- ; PERFORM AND PLOT TESTS ;-------------------------------------------- repeat i repetitions [ ;-------------------------------------------- ; PUT YOUR LOOP INIT CODE HERE: ;-------------------------------------------- tmp: last append/only blk copy [] time: time-lapse [ loop loops [ ;-------------------------------------------- ; PUT YOUR TEST CODE HERE: ;-------------------------------------------- ; here we just accumulate RAM... append tmp make string! 1000 ] ] memory: stats append plot-data reduce [ time memory] prin "." ] ;------------------------- ; extract plot data scale ;------------------------- time-x: 0:00 stat-y: 0 foreach [time stat] plot-data [ time-x: max time-x time stat-y: max stat-y stat ] time-scale: (gfx-size/y / to-decimal time-x ) mem-scale: gfx-size/y / stat-y print "" ?? time-scale ?? mem-scale ;------------------------- ; build gfx ;------------------------- ;------- ; lines i: 0 mem-line: compose [line-width lw pen (mem-color ) line () ] time-line: compose [line-width lw pen (time-color ) line () ] foreach [time ram] plot-data [ time: to-decimal time ;?? time ;print time * time-scale append mem-line margins + to-pair reduce [ x: to-integer (i / (repetitions - 1) * gfx-size/x) to-integer ( ram * mem-scale )] append time-line margins + to-pair reduce [ x to-integer ( time * time-scale )] i: i + 1 ] ;------ ;scales scale-drw: compose [ line-width 1 pen (border-color) box (margins) (margins + gfx-size) ] repeat i (slices + 1) [ ii: i - 1 append scale-drw reduce [ 'pen mem-color 'text margins + to-pair reduce [ -50 (gfx-size/y - (ii / slices * gfx-size/y ) ) - 5 ] rejoin [ to-string round/to (ii / slices * stat-y / 1'000'000) 0.01 " MB" ] 'pen time-color 'text margins + to-pair reduce [ gfx-size/x (gfx-size/y - (ii / slices * gfx-size/y ) ) - 5 ] rejoin [ to-string round/to (1000 * ii / slices * to-decimal time-x) 0.1 "ms" ] 'pen border-color 'text margins + to-pair reduce [ ((ii / slices * gfx-size/x ) ) gfx-size/y + 10 ] rejoin [ to-string to-integer( ii / slices * repetitions) ] ] ] view layout compose/deep [ box (margins * 2 + gfx-size) bg-color effect [draw [ translate (0x1 * (margins * 2 + gfx-size)) scale 1 -1.0 (mem-line) (time-line) reset-matrix (scale-drw) ]] ]
随意复制和编辑这个脚本,它是一个非常简单的绘图机制……我相信你可以添加额外的曲线(就像你正在测试的函数的输出值一样).
在这个脚本的顶部你可以看到有一个禁用GC的选项……在这个循环中的测试代码中,我们只是分配RAM,看看GC如何影响整体性能.
这是启用GC的运行:
您可以看到执行受到阻碍,因为GC会持续监控并中断流程.
随着RAM增加(红线),执行不断减慢.请注意,所有块都已预先分配,因此这不是由于内部存储器复制.
这是禁用GC的运行:
你可以清楚地看到操作的线性程度.
两次运行中的不规则跳跃是由于正常的OS多任务处理.
注意两个测试中的最佳速度是相同的.大约5毫秒…但平均值,只是在启用GC的运行中变得越来越糟.
出于这个原因,当我测试任何代码时,我总是关闭GC,除非代码生成的直接内存快速运行任务内存.或者,您可以在代码中的关键点回收/关闭回收/开启,以缓解问题中的这部分问题.当调用recycle / on时,如果超出阈值,它将立即进行清理.
请注意,内存分配复杂性会对GC产生影响.它不仅是RAM的数量,而且是正在构建的值的数量,这将产生很大的后果.使用上面的分析器,您可以构建指数级慢的算法,当它们实时开始时,它们几乎不能用于300MB.
绑定和创建许多复杂的对象,是完全摧毁任何反叛速度假装的东西.如果要创建大型数据集,最好使用块层次结构而不是对象层次结构.
我希望上面的脚本可以帮助您分析一些您想要测试的内容.