-
-
Save giesse/1232d7f71a15a3a8417ec6f091398811 to your computer and use it in GitHub Desktop.
Red [] | |
e.g.: :comment | |
; ideally not exported on the global context | |
delta-time*: function [code count] [ | |
start: now/precise | |
loop count code | |
difference now/precise start | |
] | |
delta-time: function [ | |
"Return the time it takes to evaluate a block" | |
code [block! word! function!] "Code to evaluate" | |
/accuracy run-time [time! none!] "Longer time gives more accurate results, but takes longer to compute. Default 0:00:00.1" | |
][ | |
run-time: any [run-time 0:00:00.1] | |
time: 0:00:00 | |
count: 1 | |
if word? :code [code: get code] | |
cd: either block? :code [code] [[code]] | |
while [time < run-time] [ | |
time: delta-time* cd count | |
; if your computer is really really fast, or now/precise is not accurate enough | |
; (hello Windows users!) | |
either time > 0 [ | |
result: time / count | |
; multiply by 1.5 for faster convergence | |
; (ie. aim for 1.5*run-time) | |
count: to integer! run-time * count * 1.5 / time | |
] [ | |
count: count * 10 | |
] | |
] | |
result | |
] | |
runs-per: function [ | |
"Return the number of times code can run in a given period" | |
code [block! word! function!] "Code to evaluate" | |
time [time!] | |
][ | |
t: delta-time/accuracy :code time | |
to integer! time / t | |
] | |
format-time: function [ | |
"Convert a time value to a human readable string" | |
time [time!] | |
] [ | |
if time >= 0:00:01 [ | |
; work around a bug in the current stable release | |
time: form round/to time 0.001 | |
if decimals: find/tail time #"." [ | |
clear skip decimals 3 | |
] | |
return time | |
] | |
units: ["ms" "μs" "ns" "ps"] | |
foreach u units [ | |
time: time * 1000 | |
if time >= 0:00:01 [ | |
time: to integer! round time | |
return append form time u | |
] | |
] | |
] | |
print-table: function [ | |
"Print a block of blocks as an ASCII table" | |
headers [block!] | |
block [block!] | |
] [ | |
format: clear [] | |
header: clear [] | |
sep: [] | |
i: 1 | |
unless parse headers [ | |
some [ | |
(text: width: fmt-func: none) | |
set text string! any [set width integer! | set fmt-func word! | set fmt-func path!] | |
( | |
append header sep | |
append header either width [pad text width] [text] | |
either width [ | |
either fmt-func [ | |
append format compose [(sep) pad (fmt-func) pick block (i) (width)] | |
] [ | |
append format compose [(sep) pad pick block (i) (width)] | |
] | |
] [ | |
either fmt-func [ | |
append format compose [(sep) (fmt-func) pick block (i)] | |
] [ | |
append format compose [(sep) pick block (i)] | |
] | |
] | |
sep: "|" | |
i: i + 1 | |
) | |
] | |
] [ | |
cause-error "Invalid headers spec" | |
] | |
print header | |
format: func [block] reduce ['print format] | |
foreach row block [format row] | |
] | |
; Putting the runtime first in results, and memory second, helps things | |
; line up nicely. It's a problem if we want to add more stats though, | |
; as any code using the data with expected field indexes will break if | |
; we don't add the new stats at the end. We could use named fields as | |
; well but, for now, we'll stick with this and let this comment serve | |
; as a warning. More stats will certainly come in the future, as will | |
; GC, but this is just a quickie function in any case. | |
; Memory stats and formatted output added by @toomasv. | |
profile: function [ | |
"Profile code, returning [time memory source] results" | |
blocks [block!] "Block of code values (block, word, or function) to profile" | |
/accuracy run-time [time!] "Longer time gives more accurate results, but takes longer to compute. Default 0:00:00.1" | |
/show "Display results, instead of returning them as a block" | |
][ | |
baseline: delta-time/accuracy [] run-time | |
res: collect [ | |
foreach blk blocks [ | |
; I'm not convinced about the significance of memory stats when computed this way, | |
; but I'm going to leave it here -Gab | |
stats-1: stats ; get current stats before evaluation | |
n: subtract delta-time/accuracy :blk run-time baseline | |
keep/only reduce [ | |
n | |
stats - stats-1 | |
; any practical purpose for copying blk here? -Gab | |
either block? :blk [copy blk][:blk] | |
] | |
] | |
] | |
sort res ; sort by time | |
either show [ | |
unless empty? res [ | |
reference: res/1/1 | |
] | |
fmt-time: function [time] [ | |
rel: time / reference | |
rejoin [round/to rel 0.01 "x (" format-time time ")"] | |
] | |
print-table [ | |
"Time" 20 fmt-time | |
"Memory" 11 | |
"Code" mold/flat | |
] res | |
][ | |
insert/only res copy [Time Memory Code] | |
new-line/all res on ; Return formatted results | |
] | |
] | |
e.g. [ | |
probe profile [] | |
profile/show [] | |
print "" | |
probe profile [[wait 1] [wait .25] [wait .5]] | |
probe profile [[100 / 1 * (100 / 1)] [100.0 / 1.0 ** 2] [100% / 1%]] | |
one: [1 + 1] | |
two: [2 + 2] | |
probe profile [one two] | |
print "" | |
profile/show [[wait 1] [wait .25] [wait .5]] | |
print "" | |
profile/show [[100 / 1 * (100 / 1)] [100.0 / 1.0 ** 2] [100% / 1%]] | |
print "" | |
profile/show [one two] | |
print "" | |
b1: [wait .25] | |
b2: [wait .5] | |
profile/show reduce [b1 b2] | |
print "" | |
f1: does [wait .25] | |
f2: does [wait .5] | |
profile/show reduce [:f1 :f2] | |
] |
Red [] | |
e.g.: :comment | |
; ideally not exported on the global context | |
delta-time*: function [code count] [ | |
start: now/precise | |
loop count code | |
difference now/precise start | |
] | |
delta-time: function [ | |
"Return the time it takes to evaluate a block" | |
code [block! word! function!] "Code to evaluate" | |
/count ct [integer! time! none!] "Eval the code this many times, rather than once; if time! determine automatically" | |
][ | |
if word? :code [code: get code] | |
cd: either block? :code [code] [[code]] | |
ct: any [ct 0:00:00.1] | |
either time? ct [ | |
run-time: to float! ct | |
time: 0 | |
count: 1 | |
while [time < run-time] [ | |
time: to float! delta-time* cd count | |
; if your computer is really really fast, or now/precise is not accurate enough | |
; (hello Windows users!) | |
either time > 0 [ | |
result: time / count | |
; multiply by 1.5 for faster convergence | |
; (ie. aim for 1.5*run-time) | |
count: to integer! run-time * count * 1.5 / time | |
] [ | |
count: count * 10 | |
] | |
] | |
; because of time! limited accuracy, we return time needed to run 10000 times. | |
to time! 10000 * result | |
] [ | |
delta-time* cd ct | |
] | |
] | |
runs-per: function [ | |
"Return the number of times code can run in a given period" | |
code [block! word! function!] "Code to evaluate" | |
time [time!] | |
][ | |
t: delta-time/count :code time | |
to integer! 10000 * time / t | |
] | |
; Putting the runtime first in results, and memory second, helps things | |
; line up nicely. It's a problem if we want to add more stats though, | |
; as any code using the data with expected field indexes will break if | |
; we don't add the new stats at the end. We could use named fields as | |
; well but, for now, we'll stick with this and let this comment serve | |
; as a warning. More stats will certainly come in the future, as will | |
; GC, but this is just a quickie function in any case. | |
; Memory stats and formatted output added by @toomasv. | |
profile: function [ | |
"Profile code, returning [total-time time memory source] results" | |
blocks [block!] "Block of code values (block, word, or function) to profile" | |
/count ct [integer! time!] "Eval code this many times, rather than determine automatically; or time! for accuracy (longer = more accurate)" | |
/show "Display results, instead of returning them as a block" | |
][ | |
ct: any [ct 0:00:00.1] | |
baseline: delta-time/count [] ct | |
count: either time? ct [10000] [ct] | |
res: collect [ | |
foreach blk blocks [ | |
; I'm not convinced about the significance of memory stats when computed this way, | |
; but I'm going to leave it here -Gab | |
stats-1: stats ; get current stats before evaluation | |
n: subtract delta-time/count :blk ct baseline | |
keep/only reduce [ | |
round/to n .001 | |
round/to n / count .001 | |
stats - stats-1 | |
; any practical purpose for copying blk here? -Gab | |
either block? :blk [copy blk][:blk] | |
] | |
] | |
] | |
sort res ; sort by time | |
either show [ | |
print ["Count: " count] | |
template: [pad (time) 12 #"|" pad (time-per) 12 #"|" pad (memory) 11 #"|" (mold/flat :code)] | |
insert/only res ["Time" "Time (Per)" "Memory" Code] ; last column is molded, so not a string here | |
foreach blk res [ | |
set [time: time-per: memory: code:] blk | |
print compose template | |
] | |
][ | |
insert/only res compose [count: (count) fields: [Time Time-Per Memory Code]] | |
new-line/all res on ; Return formatted results | |
] | |
] | |
e.g. [ | |
profile [] | |
profile/show [] | |
profile [[wait 1] [wait .25] [wait .5]] | |
profile/count [[100 / 1 * (100 / 1)] [100.0 / 1.0 ** 2] [100% / 1%]] 1000000 | |
one: [1 + 1] | |
two: [2 + 2] | |
profile [one two] | |
profile/show [[wait 1] [wait .25] [wait .5]] | |
profile/show/count [[100 / 1 * (100 / 1)] [100.0 / 1.0 ** 2] [100% / 1%]] 1000000 | |
profile/show [one two] | |
b1: [wait .25] | |
b2: [wait .5] | |
profile/show/count reduce [b1 b2] 2 | |
f1: does [wait .25] | |
f2: does [wait .5] | |
profile/show/count reduce [:f1 :f2] 2 | |
] |
Right... but personally I'd rather give names to the blocks and show the names instead (eg. pass a map instead of a block of blocks). Admittedly you normally use this on the console and you're just calling a function, so it's ok as it is, but in general just molding the code block doesn't sound like a great idea to me.
doesn't sound like a great idea to me.
Why?
Because code doesn't really fit well in a table cell.
Well, that's not mold
's problem ;)
At least for me, mold/flat/part
is usually okay because I can guess the code by first 50-100 chars.
Next step is an IDE. Or is there something in between?
I've never said that it was mold
's problem :) It's just not ideal in this kind of situation. That being said, I use it all the time too, and as I said above, given how profile
is generally used, mold
is probably ok. The code would need to be molded before being executed though, to avoid the problem you mentioned.
copy/deep
is a necessary evil sometimes too, e.g. in my in-place profiler I cannot mold because I do not know the bounds of each expression before they are evaluated. And after they are evaluated they may change already. So, literal maps are not pretty printed :(
@giesse the memory reporting is not correct, I see this:
>> profile/show [[make string! 5]]
Time | Memory | Code
1.0x (323ns) | -181080 | [make string! 5]
>> profile/show [[make string! 5]]
Time | Memory | Code
1.0x (317ns) | 510288 | [make string! 5]
This is a wrong solution actually, ideally there should be
mold
. Try it withprofile/show [[repeat i 1000 [put #() i 1]]]
to see the rationale.