4

我最近询问了在 Rebol 中计算换行符的最快/最有效的方法——我现在需要确定在给定情况下哪种方法最好。

一些示例场景:短文本,更少的换行;短文本,许多换行符;中/长文本,许多换行符(代码);中/长文本,更少的换行符(文章)。

我有一些不确定性:如果我一个接一个地运行,第二个测试会被第一个测试污染吗?对于不同的场景(优化)需要不同的功能与一个功能适合所有人(方便)会有多大的差异?我需要 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
    ]
]
4

2 回答 2

6

在 REBOL(R2 和 R3)中进行分析的挑战是三重的。定时、循环和内存使用。

定时:

在某些操作系统上,默认时间并不精确(如 Windows)。这可以通过创建更大的循环来很大程度上缓解,这些循环基本上将测试扩展到可接受的时序裕量。您还可以创建更好的计时器,例如我为 windows ( chrono-lib.r ) 构建的 chrono lib。

它还包括一个延时功能,使测试块变得容易(基本上是一个更精确的 'DT for windows)。

循环:

这是一件显而易见的事情,当您计时,为了消除一些操作系统/多任务开销,您运行代码多次并将重复次数除以得到平均值。这通常足以得到一个好主意。

但是,在 rebol 中,循环本身的成本相当高。迭代函数有它们自己的不可忽略的开销。因此,在循环某些代码之前,请确保您使用的循环是最佳的,尤其要确保您使用本机迭代器函数,因为循环最终可能比您尝试分析的代码慢。在 R2 中,FOREACH 和 LOOP 在大多数情况下是更快的循环。FOREACH 在开始循环之前有一点绑定开销,但在我的广泛测试中,这产生了不可估量的影响,因为它只发生一次并且与几秒钟的运行无关。

在 R3 中,由于 R2 中的一些夹层循环已经成为原生的,因此迭代器函数得到了速度提升,因此您必须检查它们才能看到。

记忆:

这是事情变得难以预测的地方。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 的运行: 在此处输入图像描述

您可以清楚地看到操作的线性程度。

您在两次运行中的不规则跳跃是由于正常的操作系统多任务处理造成的。

请注意,两个测试中的最佳速度是相同的。大约 5 毫秒……但平均而言,在启用 GC 的运行中变得越来越糟。

出于这个原因,当我测试任何代码时,我总是将 GC 关闭,除非代码生成的即时内存很快将任务运行到内存不足。或者,您可以在代码中的关键点回收/关闭回收/开启,以缓解这部分问题。当调用 recycle/on 时,如果超出阈值,它将立即进行清理。

请注意,内存分配的复杂性对 GC 有影响。不仅是 RAM 的数量,而且正在构建的值的数量也会产生很大的影响。使用上面的分析器,您可以构建指数级缓慢的算法,当它们开始实时时,这些算法在 300MB 时几乎无法使用。

绑定和创建许多复杂的对象,这将完全破坏 rebol 中任何速度的伪装。如果要创建大型数据集,最好使用块层次结构而不是对象层次结构。

我希望上面的脚本能帮助你描述一些你想要测试的东西。

于 2013-02-21T21:13:08.007 回答
4

R3 中对高分辨率时间的更改中,Carl Sassenrath 描述了 Rebol 3 中改进的(超过 Rebol 2)计时器。您可以获得stats/timer分辨率为几微秒的时间戳。要测量时间增量,delta-time请按如下所示使用,但请参阅有关在 CPU 频率变化中获取增量的准确性的博客文章。

其中发现了几个要点:

  • 在启动时,在任何初始化之前,它被设置为 0:00:00.0
  • 在分频之前计算具有高分辨率计时器滴答声的增量(在 Win32 上)

和一些例子:

准确显示总启动时间的脚本:

Rebol [file: %boot-time.r]
print stats/timer

正弦函数计时的简单示例:

delta-time [loop 100 [sine 123]]
于 2013-02-21T23:15:46.640 回答