Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to convert (many!) numbers to strings without allocations in Qt

tl;dr

I want to call QString::number(int) many times per second. It is very slow: seems like it allocates a new string each time. Tried to use setNum on same string instead, still no joy.


Original, long question:

The problem

I have a big array of numbers (say, integers) and I want to format them into text, that will be then (maybe not immediately) written to file. Naive way looks approximately1 like this:

QString allData;
foreach(const int & value, values) {
    allData += QString::number(value);
    allData += '\n';
}

It takes about 280ms for 150000 integers on my machine which seems to much for me. I suppose that this is because QString::number is called 150000 times and each time allocates new string. This is someway confirmed to be the root of the problem when I try to use itoa (which does not allocate memory) instead.

Possible, but not-Qt [not-cute] solution

QString allData;
char buffer[100];                               // <-------
foreach(const int & value, values) {
    _itoa_s(value, buffer, sizeof(buffer), 10); // <-------
    allData += buffer;
    allData += '\n';
}

This takes about 70ms for the same 150000 integers (about 4x faster) which is by now acceptable for me (I think I can do something with string concatenation as well, but let's leave this outside this question)

But I don't like that I have to use some unstandard, probably deprecated, probably unportable2 function (not to say that this just looks ugly).

Then I remembered that there is also an instance method: QString::setNum. I hoped I could use the same pattern as with itoa: have only one string allocated and modify it each time.

Desirable, but not working solution

QString allData;
QString number;                       // <-------
foreach(const int & value, values) {
    number.setNum(value);             // <-------
    allData += number;
    allData += '\n';
}

Unfortunately, this doesn't make big difference from QString::number: again about 280ms, well, maybe 250ms but still too much.

So, congrats if you reached here :) and finally...

The Question(s)

  1. What would Qt experts advise me to do? Shut up and use itoa despite the distinct smell of C in otherwise fragrant C++/Qt code?
  2. Or can I somehow say "C'mon, Qstring, just eat this number into you" ?
  3. I wonder why setNum did not do the trick?

Footnotes:

1 In actual code i have not just 150000 integers, but 50000 triples of integers which I also add '\t' between them. This is the only difference from my actual code and I guess it is not important: here I'm interested only in performance of QString::number vs itoa.

2 Actually, I was surprised that MinGW also has _itoa_s that behaves just like as Visual Studio's, but I still have some awkward feeling that using such a dirty function in my polished Qt code reduces its portability. Correct me if I'm wrong.

like image 245
NIA Avatar asked May 03 '14 11:05

NIA


2 Answers

You can try with QByteArray that shares same QString's interface but is more suitable for performance issues.I obtain 36 ms (qt 5.2 clang) vs. your original 57 ms (on my machine) with this code:

QByteArray allDatab;
foreach(const int & value, values) {
    allDatab += QByteArray::number(value);
    allDatab += '\n';
}
QString result(allDatab);

and 29 ms with this version (that maybe confirm your assumptions about setNum):

QByteArray allDatad;
QByteArray number;                       
foreach(const int & value, values) {
    number.setNum(value);             
    allDatad += number;
    allDatad += '\n';
}
like image 171
Salvatore Avanzo Avatar answered Nov 14 '22 20:11

Salvatore Avanzo


How about using STL?

I tested your code (modifying the loop to simplify)

int main() {
  stringstream ss;
  for(int i=0; i<2000000; ++i) {
     ss << i << "\n";
  }
}

And I obtain

time ./ss_test
  real  0m0.146s
  user  0m0.139s
  sys   0m0.006s

With Qt version (in my machine)

int main() {
  QString allData;
  for(int i=0; i<2000000; ++i) {
    allData += QString::number(i);
    allData += '\n';
  }
}

I obtain

time ./qtstring_test 
   real 0m0.516s
   user 0m0.508s
   sys  0m0.008s
like image 33
el aurens Avatar answered Nov 14 '22 20:11

el aurens