ch = ++index < strlen ? str.charAt(index) : 0;
If I saw that code scattered across in some java code, I would immediately be like - "rewrite please".But the same code fragment in C would be like - yeah, we need a bounds check, good job, can you make it a macro please.
The java default Float parser was somewhat of a complete perf headache when multi-threading, more than about single core consumption (till jdk8b96 - JDK-7032154), so in the past Hive has shipped with its own strod impl[1] to avoid the central lock in parseDouble.
Also it worked off a raw utf8 byte[] stream, which is more common coming out of a disk file or network serialization rather than a CharSequence.
[1] - https://github.com/apache/hive/blob/4446414f4478091db1eb20bc...
Code should be written for humans.
They could produce something that looks like Qt, OWL/VCL, or nowadays .NET like but in C++.
Instead they produce libraries where one is forced to use C low level details, mixed with Win32 and COM pointer handling and casts everywhere, not even MFC escapes it.
Not to count how many interactions of COM smart pointers that have come up with since COM exists.
C++/CX looked like they finally learned their way and would offer something similar to C++ Builder, but no that wasn't meant for us to enjoy it.
So now anyone doing UWP with C++, gets to manually edit IDL files without tooling support, and merge the generated files manually.
I really don't get their macho developer culture.
In retrospect, do you feel it was it a bad idea to not use a heavier abstraction?
I'm inclined to not recognize that as a distinct culture and rather call it an infectious habit. Auditable, maintainable code is a skill, after all.
let ch = if index < strlen then str.charAt(index) else 0
Eminently readable and less boring than long if/else chains in Python.On a side note, having gotten used to newer languages, an option type sure is a nice thing.
I'd far prefer:
++index;
ch = index < strlen ? str.charAt(index) : 0;
I don't see a good reason to make it a macro, though. index += 1;
if (index < strlen) {
ch = str.charAt(index);
} else {
ch = 0;
}
If I was using kotlin, I'd do it even cleaner: inline fun String.at(index: Int): Char? =
if (index >= 0 && index < length) charAt(index)
else null
[...]
index += 1
ch = str.at(index) ?: 0;
The bytecode is exactly the same, but I’d argue it’s much cleanerC++:
bool found_minus = (*p == '-');
bool negative = false;
if (found_minus) {
++p;
negative = true;
if (!is_integer(*p)) { // a negative sign must be followed by an integer
return nullptr;
}
}
Java: int strlen = str.length();
if (strlen == 0) {
throw new NumberFormatException("empty String");
}
int index = 0;
char ch = str.charAt(index);
boolean negative = false;
if (ch == '-') {
negative = true;
ch = ++index < strlen ? str.charAt(index) : 0;
if (ch == 0) {
throw new NumberFormatException("'-' cannot stand alone");
}
}I have seen far too many bugs creep in this way. I feel like C/C++ induce a desire to keep the code as short as possible and being 'clever'; even I often feel the need to do that in C++ when I am happy to write a more readable version in other programming languages.
The bugs are also hard to spot when skimming through the code: When there's a lot of code you will often miss the ++ on the 'index' as it's a long line compared to having '++index' on its own line.
Another good 'pet peeve' of mine is the following construct:
for(int i = 10; i--;)
// Do something until 'i' reaches 0
So many off-by-one errors...The source of charAt is also interesting because it manually bounds checks while the array access into the underlying char[] has its own bounds check. I'm not sure why having the separate exception is better than letting the array access throw directly?
https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c...
Swift originally had an option named -Ofast that just turned off all the safety features - eventually it got renamed to -Ounchecked.
I still don't think it's very descriptive though. Whether or not it's unsafe depends a lot on the domain. In most of my code, I don't care about the sign of zero or floating point associativity and want my compiler to auto convert division to multiplication by reciprocals. Better would be something like -fnon-compliant-float-math or something.
Although I guess -fno-trapping-math is a bit different from the other optimizations which could actually be "unsafe" in a different way, since signaling nans are quite useful for debugging.
Apple M1 OpenJDK 64-Bit Server VM, Azul Systems, Inc., 16+36
parsing random integers in the range [0,1)
=== number of trials 32 =====
FastDoubleParser.parseDouble MB/s avg: 492.324205, min: 479.57, max: 516.26
Double.parseDouble MB/s avg: 111.509085, min: 110.63, max: 114.65
Speedup FastDoubleParser vs Double: 4.415104
Apple M1 OpenJDK 64-Bit Server VM, Azul Systems, Inc., 16+36
parsing integers in file data/canada.txt
read 111126 lines
=== number of trials 32 =====
FastDoubleParser.parseDouble MB/s avg: 483.642065, min: 420.43, max: 502.15
Double.parseDouble MB/s avg: 104.347746, min: 99.03, max: 116.91
Speedup FastDoubleParser vs Double: 4.634907
https://github.com/ulfjack/ryu/blob/master/src/main/java/inf...
Aside, my JMathTeX fork uses Ryū to achieve real-time rendering of TeX in Java.
(Though 1/100M randomly tested conversions had a 14ULP inaccuracy, so I guess the inaccuracy is a bit unpredictable).
Anyway, it's all great programming but it seems to me a symptom or a larger social failure, where we are making bad protocols faster instead of making the protocols better.
That's why I'm building Concise Encoding [1], which is a twin text/binary format that can be 1:1 converted back and forth. You do everything in binary, and only convert to/from text in the few places where a human needs to be involved (like debugging or configuration).
Lemire's really has made a huge contribution to computer science
I also don’t think parsing doubles will ever be the bottleneck in any of my Java code. If it turns out it is, though...
In my C# and even in C++, profiler sometimes showed me I am bottlenecked on parsing or printing floats. Happened quite a few times.
In all cases the data was 3D models or similar things. STL, 3MF, VTK, G-mesh, G-code are all text based, either always (looking at you, 3MF consortium) or as an option (STL and some versions of VTK can be either binary or text). Even low-end modern GPUs can easily render 1E+7 triangles these days, thus high-poly 3D models are not uncommon.
Games are not affected because they preprocess the hell out of their assets, optimizing everything they can about the data format. But in CAD/CAM stuff you’re pretty much guaranteed to bottleneck on parsing numbers when loading these text-based formats.
Though I would have added a unit test to iterate over complete 2^64 set of values and compare the output to Double.parseDouble().
Also, while there are only 2^64 distinct double values, there are many more string representations than that. For example, "1" and "1.0" are the same underlying value, but a parser needs to be able to handle both.