-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use dragonbox algorithm; alternative to #18008 #18139
Conversation
after benchmarking this PR vs #18008 (with -d:danger), this results in slower code, loosing important benefits of using dragonbox for people interested in performance, float to string can be a bottleneck in some applications; in addition, this causes significantly slower code in VM for I'd rather follow your suggestion from #18008 (comment)
and update the original PR to do that instead, deferring the (unrelated: the bootstrap failure in this PR is yet another reason why updating csources_v1 to a recent nim release reduces friction when writing compiler code; in this case >= 1.4.0 would make it work; refs timotheecour#251); at least now we have a better CI/build script integration that should correctly handle csources_v1 updates, and soon a nimdigger tool ready to be used |
Which benchmark, how much is it slower and why? We'll be in the "omg, we need to use existing (risky) C(++) code for good because we lose performance otherwise" world for good. I like the NIH world better -- it's also what Go/D/Rust do btw. |
But not for this case. Code like var n: int = c_sprintf(addr buf, "%.16g", value)
var hasDot = false
for i in 0..n-1:
if buf[i] == ',':
buf[i] = '.'
hasDot = true
elif buf[i] in {'a'..'z', 'A'..'Z', '.'}:
hasDot = true
if not hasDot:
buf[n] = '.'
buf[n+1] = '0'
buf[n+2] = '\0'
result = n + 2
else:
result = n
# On Windows nice numbers like '1.#INF', '-1.#INF' or '1.#NAN' or 'nan(ind)'
# of '-1.#IND' are produced.
# We want to get rid of these here:
if buf[n-1] in {'n', 'N', 'D', 'd', ')'}:
writeToBuffer(buf, "nan")
result = 3
elif buf[n-1] == 'F':
if buf[0] == '-':
writeToBuffer(buf, "-inf")
result = 4
else:
writeToBuffer(buf, "inf")
result = 3 does scream "we need to be in control of the library!"... |
1.2 to 1.6 X slower depending on distribution (in VM it's up to 2.8X slower, it matters less but still) as to why, i don't know yet, but it could be due to something getting lost in c2nim/manual postprocessing translation ; i tried different things including replacing -d:danger with --passc:-O3 --stacktrace:off --checks:off to get closer to what original PR uses to compile dragonbox.cc, but no effect there While 1.6X doesn't matter for most programs, it will matter for some programs where string to float is on the critical path, and it's easy enough to just erase the gap. |
Independent CI failure, merging. |
If one must use the C++ version, would it be possible to use patchFile to substitute an alternate implementation? |
@Araq here's the benchmark: results(taking best of 5 runs independently for CT vs RT, but it doesn't vary much between runs) PR #18139 (which just got merged in devel): PR #18008 is ~1.4X faster at RT and ~2.5X faster at CT compared to #18139 details
benchmark code# t12341.nim:
when true:
import std/times
when defined case_pr_18008:
# PR https://github.com/nim-lang/Nim/pull/18008
# 3080108d57ea553142036764a118921845c9005f, as of revert the diff in system.nim (moved it to another PR)
import std/strfloats
const N = 64
else:
# after 63db2b19bf0787db89ce89c8861e976d44e33cbd https://github.com/nim-lang/Nim/pull/18139
import system/formatfloat
const N = 65
const start = cast[uint](1.0)
template getFloat(i): float =
cast[float](start + i) # consecutive floats
# cast[float](start + (i * 2038074743)) # consecutive floats spaced by the 100000000'th prime
proc main() =
var s = ""
var c = 0
var n = 0'u
when nimvm:
n = 1_000_000'u # can increase if you also increase `--maxLoopIterationsVM`
else:
n = 100_000_000'u
var buf: array[N, char]
let t = cpuTime()
for i in 0'u ..< n:
let f = getFloat(i)
when nimvm:
s.setLen 0
s.addFloat f
c+=s.len
else:
# fastest float to string (avoiding addFloat overhead)
when defined case_pr_18008:
let m = toString(buf, f)
else:
let m = writeFloatToBuffer(buf, f)
c+=m
echo (c, cpuTime() - t)
static: main() # requires # --experimental:vmopsdanger -f
main() conclusions
var a = 1.1'f32
doAssert $a == "1.1", $a # fails as explained in #18008 (comment), Drachennest-schubfach_32 seems like the simplest approach, as I did here: timotheecour#732 using lib/vendor/drachennest/schubfach_32.cc from https://github.com/abolz/Drachennest/blob/master/src/schubfach_32.cc; this would be the code to port/wrap
|
With the float string representation fixed in Nim devel (See nim-lang/Nim#18139), that uncovered a bug in the logic for parsing float TOML values. For e.g. to parse "foo = 0.123", internally, 0.1 + 0.02 + 0.003 was done which would evaluate to 0.12300000000000001. Earlier float stringification bug caused that value to print out as "0.123" instead of "0.12300000000000001". This is now fixed by using parseutils.parsefloat, which parses the "0.123" float value as 0.123 and not 0.12300000000000001. Fixes NimParsers#45.
With the float string representation fixed in Nim devel (See nim-lang/Nim#18139), that uncovered a bug in the logic for parsing float TOML values. For e.g. to parse "foo = 0.123", internally, 0.1 + 0.02 + 0.003 was done which would evaluate to 0.12300000000000001. Earlier float stringification bug caused that value to print out as "0.123" instead of "0.12300000000000001". This is now fixed by using parseutils.parsefloat, which parses the "0.123" float value as 0.123 and not 0.12300000000000001. Fixes NimParsers#45.
With the float string representation fixed in Nim devel (See nim-lang/Nim#18139), that uncovered a bug in the logic for parsing float TOML values. For e.g. to parse "foo = 0.123", internally, 0.1 + 0.02 + 0.003 was done which would evaluate to 0.12300000000000001. Earlier float stringification bug caused that value to print out as "0.123" instead of "0.12300000000000001". This is now fixed by using parseutils.parsefloat, which parses the "0.123" float value as 0.123 and not 0.12300000000000001. Fixes NimParsers#45.
With the float string representation fixed in Nim devel (See nim-lang/Nim#18139), that uncovered a bug in the logic for parsing float TOML values. For e.g. to parse "foo = 0.123", internally, 0.1 + 0.02 + 0.003 was done which would evaluate to 0.12300000000000001. Earlier float stringification bug caused that value to print out as "0.123" instead of "0.12300000000000001". This is now fixed by using parseutils.parsefloat, which parses the "0.123" float value as 0.123 and not 0.12300000000000001. Fixes #45.
Fixes nim-lang#14407 . This issue was fixed by nim-lang#18139.
Fixes nim-lang#14407 . This issue was fixed by nim-lang#18139.
Fixes nim-lang#14407 . This issue was fixed by nim-lang#18139.
* use dragonbox algorithm; alternative to nim-lang#18008 * removed unsafe code
Fixes nim-lang#14407 . This issue was fixed by nim-lang#18139.
Note: the string handling code is still nuts, impossible to verify. We should rewrite it to write into var openArray and benefit from the bounds checking.