A string processing rant
fgiesen.wordpress.com
fgiesen.wordpress.com
Here is another little thing you can optimize out (probably not saving much, but still bugs me):
SoftwareOcclusionCulling/CPUT/CPUT/CPUT.h:#define SAFE_DELETE(p) {if((p)){HEAPCHECK; delete (p); (p)=NULL;HEAPCHECK; }}
SoftwareOcclusionCulling/CPUT/CPUT/CPUT.h:#define SAFE_DELETE_ARRAY(p){if((p)){HEAPCHECK; delete[](p); (p)=NULL;HEAPCHECK; }}
There is no need to check for NULL array or pointer before calling delete[] or delete, the standard allows for it.Lots of stuff coming from Intel is just badly written for some reason. Few years ago there was the SMOKE demo using Thread Building Blocks, and there were O(N^2) and even O(N^3) things for the particles.
Intel, you are supposed to show us how to code to the metal... Or maybe their ICC compiler is specifically better if you write this way (lol, what a conspiracy).
I admit it's ugly, but the code below removes whitespace using only standard C++ calls. There's also an isspace() function from the <locale> header that takes a specific locale, but I'm not sure it's important for whitespace.
std::wstring removeSpaces(std::wstring theStr) {
return std::wstring(theStr.begin(),
std::remove_if(theStr.begin(),
theStr.end(),
std::iswspace));
}
There's also codec conversion in the <locale> header [1]. It's also pretty ugly, but it's easier to understand than his home grown solution, and if nothing else, is already debugged. And to be honest, if you're using C++ you should be used to the code being verbose and ugly. std::wstring remove_side_spaces(const std::wstring& str) {
auto begin = std::find_if_not(str.begin(), str.end(), std::iswspace);
if (begin == str.end()) {
return std::wstring();
} else {
auto end = std::find_if_not(str.rbegin(), str.rend(), std::iswspace);
return std::wstring(begin, end.base());
}
}This works, but I don't like the &strt and &endp:
std::wstring trim(std::wstring theStr) {
auto strt = std::find_if_not(theStr.begin(), theStr.end(), std::iswspace);
if (strt == theStr.end()) return L"";
auto endp = std::find_if_not(theStr.rbegin(), theStr.rend(), std::iswspace);
return std::wstring(&*strt,&*endp+1);
}
The easiest way is to use Boost's string algorithms library, though: std::wstring trimb(std::wstring theStr) {
boost::trim(theStr);
return theStr;
}For assets that don't make much sense merging (polygon data) - we are moving to binary data. For the rest (level data), we might keep text files - some merging is possible there.
The only time we'll ever need UTF8, UTF16, etc. is for the localized data - game text, etc. There is absolutely no need for such encoding to be in your assets.
Nitpick: you probably meant non-7 bit (there's a reason why ruby and python2 default to non-ext ascii source code, and barf at you otherwise)
7-bit (8-bit) ASCII has a long life to live :) It's just machine optimal, and the same time not flexible for modern days needs... just like "C", and I love it.
I'm using Qt5 these days, and there is QString and QByteArray (and also QLatin1String) - and I need to keep in memory lots of properties for our editor, going to QString (wide-chars) just uses more memory for no reason. Sticking to QByteArray/QLatin1String makes more sense for them.
(I'm not familiar with graphical / texture code)
But I am familiar with fast text processing. From Java the problem is even worse: it's not so much that the methods are inefficiently written in themsevles. The issue is that string processing tends to create lots of garbage which then needs to be garbage collected.
The optimization when all these strings processing methods are the bottleneck? Do not create hundreds of thousands or millions of object. Instead of doing, say, "s2 = removeLeadingAndTrailingStuff();" and then "parse(s2);" you do write your own method directly: "parseButDoNotTakeIntoAccountLeadingAndTrailingStuff()".
It's oversimplified and crazy camel case is used above to make a point ; )
It's always the same : when objects creation become the bottleneck, make it so you're not creating that many objects.
It's exactly what the LMAX Disruptor "pattern" is about: using gigantic arrays of primitives and hardly any object creation at all (to process 12 million events per second in Java on a single core!).
That said in TFA I still don't understand why several megabytes of Unicode text files have to be processed...
Just guessing, but it's very coder-friendly to have particular kinds of data available as text. For example the (old, old) Wavefront .obj format is a text based 3d model format that's ridiculously easy to parse. I could imagine someone would choose to do the same with, say, entire game levels.
That said, there's another class of programs that often need to read several megabytes worth of text files: compilers. Who's to say they're not parsing something programming-languagey?
3d graphics applications include shaders, which are programs that get compiled by the graphics drivers and loaded directly into the graphics pipeline on the video card. The source code is standardized, but the binaries vary from card to card and driver to driver, so applications have to include the source and let the driver build it into working shaders. The text being processes is possibly shader source code.
I guess the difference in Go is that the string abstraction and the slice abstraction is exposed to the programmer, so it will be apparent if that happens.
There is some info here but I was unable to find a good explanation: http://stackoverflow.com/questions/2909848/how-does-java-imp...
The SO question and answers are confusing because they talk about two competely separate issues: the above and the interning mechanism.
Note also the proscription against modifying the parsed path components[3] because they're shared. Since there's no support for immutable strings, you're counting on everybody to read the documentation and follow it. Admittedly, this specific restriction comes about from getting the file name information from the FilterManager in the first place, which has the explicit purpose of reducing the number of copies of paths floating around in kernel memory, but it still demonstrates the original point of needing to know who owns the memory at the end of the day.
[1]http://msdn.microsoft.com/en-us/library/windows/desktop/aa38... [2]http://msdn.microsoft.com/en-us/library/windows/hardware/ff5... [3]http://msdn.microsoft.com/en-us/library/windows/hardware/ff5...
A more elaborate 3D diffing is what's needed, like the one shown here: http://3drepo.org/ - but it's not an easy thing, and not very easy to integrate either.
Sometimes binary files might be bigger than text, not often but possible - for example if double is used for number it always takes 8 bytes, while as text it might be just two (0 and space).
Also certain source control systems (like perforce I think) can store differences only if the files are text, otherwise they have to store full copies (that's really not a big game changer, but one more little detail to be aware).
Artists, coders often would look for a material/bone by just using Find in Files/grep/etc, and although this might work for binary files, it's harder to show the data.
To make things faster, you can read them (compile) ahead of time and write the binary objects to binary files. In runtime loading them will be fast.