QT code slower than java file for processing.
-
wrote on 3 Jul 2015, 16:25 last edited by
I am new in programming in QT, I am migrating an application that I have made in java, I have found a performance problem when processing a file, the file stores coordinates in text format, to process these coordinates, I have a class that separates each coordinates and makes QString, the code is identical to java, I find that this process in java takes much less time than in QT,
In order to find the error, I created an application in both Java and QT with the affected classes , java takes 343 milliseconds to process a test file,while in QT takes 550 milliseconds and I do not understand wha is the problem, why this time difference.
Another problem that I face is, that the same code in real application,in Java takes the same time ,343 milliseconds, but in QT takes 2300 milliseconds. I don't know whatelse I can check and what I am doing wrong.
clases are:
Coordenada.cppQString Coordenada2::getComentario() { if (comentario == QString::null) { return ""; } return comentario; } void Coordenada2::setComentario(QString comentario) { this->comentario = comentario; } Coordenada2::Coordenada2() { x="0"; y="0"; z="0"; mantisa = ""; } QString Coordenada2::getMantisa() { return mantisa; } QString Coordenada2::getMantisa(QString dato) { QString mantisa = ""; for (int x = 0; x < dato.length(); x++) { switch (dato.at(x).toLatin1()) { case 'G': case 'X': case 'Y': case 'Z': case 'M': case 'H': case 'R': case 'A': case 'I': case 'J': case 'K': case 'S': case 'T': case 'P': case 'F': mantisa = mantisa % dato.at(x).toLatin1() % " "; } } return mantisa; } void Coordenada2::setMantisa(QString datos) { mantisa = getMantisa(datos); } Coordenada2::Coordenada2(QString lineacodigo) { procesaLinea(lineacodigo); setMantisa(lineacodigo); } bool Coordenada2::isX() { if (x == QString::null) { return false; } else { return true; } } bool Coordenada2::isY() { if (y == QString::null) { return false; } else { return true; } } bool Coordenada2::isZ() { if (z == QString::null) { return false; } else { return true; } } QString Coordenada2::getX() { if (x == QString::null) { return ""; } return x; } float Coordenada2::getNumeroX() { if (x == QString::null) { return 0; } return x.toFloat(); } void Coordenada2::setX(QString x) { this->x = x; anadeMantisa("X"); } void Coordenada2::setX(float x) { this->x = x; anadeMantisa("X"); } QString Coordenada2::getY() { if (y == QString::null) { return ""; } return y; } float Coordenada2::getNumeroY() { if (y == QString::null) { return 0; } return y.toFloat(); } void Coordenada2::setY(QString y) { this->y = y; anadeMantisa("Y"); } void Coordenada2::setY(float y) { this->y = y; anadeMantisa("Y"); } QString Coordenada2::getZ() { if (z == QString::null) { return ""; } return z; } float Coordenada2::getNumeroZ() { if (z == QString::null) { return 0; } return z.toFloat(); } void Coordenada2::setZ(QString z) { this->z = z; anadeMantisa("Z"); } void Coordenada2::setZ(float z) { this->z = z; anadeMantisa("Z"); } void Coordenada2::anadeMantisa(QString parametro) { if (mantisa == QString::null) { mantisa = parametro; } if (!mantisa.contains(parametro)) { mantisa = mantisa % " " % parametro; } } void Coordenada2::procesaLinea(QString codigo) { int pos = 0; codigo = codigo.trimmed(); comentario = ""; while (pos < codigo.length()) { char letra = codigo.at(pos++).toLatin1(); if (pos >= codigo.length()) { if (letra == '/') { comentario = "/"; } else { comentario = comentario % letra; } break; } switch (letra) { case 'X': if (!isNumeric(codigo.at(pos).toLatin1()) && codigo.at(pos) != '-' && codigo.at(pos) != '+') { comentario = comentario % letra; break; } x = ""; while (pos < codigo.length() && (isNumeric(codigo.at(pos).toLatin1()) || codigo.at(pos) == '.' || codigo.at(pos) == '-' || codigo.at(pos) == '+')) { letra = codigo.at(pos++).toLatin1(); x = x % letra; } x = x.trimmed(); break; case 'Y': if (!isNumeric(codigo.at(pos).toLatin1()) && codigo.at(pos) != '-' && codigo.at(pos) != '+') { comentario = comentario % letra; break; } y = ""; while (pos < codigo.length() && (isNumeric(codigo.at(pos).toLatin1()) || codigo.at(pos) == '.' || codigo.at(pos) == '-' || codigo.at(pos) == '+')) { letra = codigo.at(pos++).toLatin1(); y = y % letra; } y = y.trimmed(); break; case 'Z': if (!isNumeric(codigo.at(pos).toLatin1()) && codigo.at(pos) != '-' && codigo.at(pos) != '+') { comentario = comentario % letra; break; } z = ""; while (pos < codigo.length() && (isNumeric(codigo.at(pos).toLatin1()) || codigo.at(pos) == '.' || codigo.at(pos) == '-' || codigo.at(pos) == '+')) { letra = codigo.at(pos++).toLatin1(); z = z % letra; } z = z.trimmed(); break; default: if (letra != ' ') { do { comentario = comentario % letra; if (pos < codigo.length()) { letra = codigo.at(pos++).toLatin1(); } } while (pos < codigo.length() && letra != ' '); if (pos <= codigo.length()) { comentario = comentario % letra; } } break; } } if (this->comentario.length() > 0 && !(this->comentario.mid(0, 1)=="/")) { if (x.length()>0 || y.length()>0 || z.length() ) { if (comentario.length()>0) { this->comentario = QString::QString::null; } } } } bool Coordenada2::isComentario() { if (comentario == QString::null || comentario.length() == 0) { return false; } else { return true; } } bool Coordenada2::isNull() { if (!this->isX() && !this->isY() && !this->isZ()) { return true; } else { return false; } } bool Coordenada2::isNumeric(char cadena) { if ('0' <= cadena && cadena <= '9' ) return true; else return false; }
main.cpp
void leeArchivo(){ QFile archivo("c:\\coordenadas.txt"); int lineas = 0; float maxX = -10000; float maxY = -10000; float minX = 10000; float minY = 10000; float maxZ = -10000; float minZ = 10000; bool hayX = false; bool hayY = false; QString strLinea; Coordenada2 elementoAnterior(); if (archivo.open(QFile::ReadOnly)){ QTextStream in(&archivo); QDateTime tiempo = QDateTime::currentDateTime(); while (!in.atEnd()) { QString strline(in.readLine()); lineas++; Coordenada2 elemento(strLinea); if (maxX < elemento.getNumeroX()) { maxX = elemento.getNumeroX(); } if (minX > elemento.getNumeroX()) { minX = elemento.getNumeroX(); } if (maxY < elemento.getNumeroY()) { maxY = elemento.getNumeroY(); } if (minY > elemento.getNumeroY()) { minY = elemento.getNumeroY(); } if (maxZ < elemento.getNumeroZ()) { maxZ = elemento.getNumeroZ(); } if (minZ > elemento.getNumeroZ() ) { minZ = elemento.getNumeroZ(); } if (elemento.isX()) { hayX = true; } if (elemento.isY()) { hayY = true; } } qDebug()<<"tiempo"<<tiempo.msecsTo(QDateTime::currentDateTime()); archivo.close(); } } int main(int argc, char *argv[]) { QApplication a(argc, argv); leeArchivo(); return 0; }
-
wrote on 3 Jul 2015, 16:46 last edited by mcosta 7 Mar 2015, 16:47
Hi and welcome to devnet,
could be useful to post an example of your text file.
If you have performances issues could be useful also to use some profiling tool (like valgrind if you're on Linux or OS X).In your code you make text processing so I suggest to have a look to this section to see if your code use in the best way Qt strings
-
wrote on 3 Jul 2015, 19:24 last edited by
Mcosta thanks.
I've watched almost all links which speaks of QString, I use to concatenate% instead of +, and I added QT_USE_QSTRINGBUILDER
I use windows 7 64bit but want the application to run on Linux and Mac too.
I do not understand it is why the same code QT in the test application is faster than in real application.
Here's a piece of test file:
X0Y56.56S0 X0.08 X0.16 X0.24 X0.08 X0 Y56.4 X0.08X0.72 X0.8 X0.88 X0.96 X1.04 X1.12 X0.08 X0 Y56.4 X0.08 X1.6 X1.68 X1.76 X1.84 X1.92
thanks
-
wrote on 3 Jul 2015, 20:08 last edited by
Hi,
I suggest to use a profiler to try to understand if there's a bottleneck in your code.
Something you could try is to not use
QString
butstd:string
(orQByteArray
).
QString
uses Unicode and than in your code there're a lot of conversions from ASCII to UNICODE and from UNICODE to ASCII.
Using QByteArray you avoid all these conversion -
wrote on 3 Jul 2015, 20:36 last edited by
ok , I'll try to use QByteArray , but because the code set in the open window in the application is slower than in the sample application . where performance is dramatically changed
-
wrote on 3 Jul 2015, 20:55 last edited by
As I said, the best way to find bottlenecks is to use some profiling tool
-
wrote on 4 Jul 2015, 07:52 last edited by
I have changed from QString to QByteArray and has improved a lot, in the application of test, time has gone down to 350 milliseconds, making it as fast as java. But when I bring changes to the real application, time went down from f 2300 milliseconds to 1300 milliseconds but still far from 350 milliseconds that it takes for the test application.
I'm looking for a profiler to see what is going on. -
wrote on 4 Jul 2015, 09:04 last edited by
Sorry I can't help you but I think you can find something using Gooogle
-
wrote on 4 Jul 2015, 10:40 last edited by
Why are you storing the values as strings in the class?
You should use references when taking stuff in, to avoid an extra allocation
on each function call, also you code is not const correct, fixing that might
help the compiler optimize your code better. -
wrote on 5 Jul 2015, 05:26 last edited by
With references do you mean to use QStringRef, why my code is not const correct, could you give me an example?
Thank you.
-
With references do you mean to use QStringRef, why my code is not const correct, could you give me an example?
Thank you.
wrote on 5 Jul 2015, 06:58 last edited by Huulivoide 7 May 2015, 07:03No with references I mean reference
void myFunc(QString& str)
with const-correctness I mean that none of your functions or the parameters
they take in are marked const, even tough they are const.I take it you are not very familiar with C++, you should learn C++ properly
before using Qt. C++ can be used to make efficient programs if one knows
his way around C++, but if he doesn't things will go wrong.Your code clearly shows you have now idea about the differences of pointer
and value types, as you are doing to "null-pointer" check on variables that
can never contain null-pointers.http://www.cprogramming.com/tutorial/references.html
https://isocpp.org/wiki/faq/const-correctness/* Example of a good function / str is passed as a reference, no copy is made */ QString myFunc(const QString& str, QPushButton* mutableButton) const { QIcon icon(QString("/the/str/is/not/changed/so/it/is/const/") + str); mutableButton->setIcon(icon); // A non-const member function is called on mutableButton, // that is it is being changed somehow. So it is not const }
Also the following piece of code makes no sense.
QString Coordenada2::getY() { /* This reads as: If empty return a new version of empty Also you should use y.isEmpty() to check if the string is empty */ if (y == QString::null) return ""; // You can just do this. If it is empty the returned value will be same // as 'return "";' return y; }
-
wrote on 5 Jul 2015, 07:22 last edited by
to add something to @Huulivoide ; at the moment passing a QString by value is not so expensive because Qt uses Implicit Sharing (there's only a reference counter incremented)
-
Lifetime Qt Championwrote on 5 Jul 2015, 09:42 last edited by Chris Kawa 7 May 2015, 09:44
@mcosta Still, even with implicit sharing, creating a new instance, copying a data pointer and incrementing a refcount in a thread safe manner is a lot more expensive than simply passing a const reference (which is basically 0 footprint operation when an optimizer passes through). That's why passing "in" parameters as const references is always the preferred way.
@nurtan There are many little problems with code above. The const ref parameters are one of them but there are others.
For example constructing empty string via "". Empty strings should always be constructed withQString()
constructor. You migt say it's the same but constructing string with "" calls QString(const char*) which requires at least some code to determine the buffer is empty (it's not as there is null terminator really).Checking if string is empty by calling operator== and QString::null is also a bad idea. Whenever possible use methods like
isNull
orisEmpty
. They might do the same but also might be more efficient, e.g. by just checking internal character count (integer comparison instead of string comparison).Another big problem is storing everything as strings, even things that are evidently numbers, like x, or mantissa. String comparison is never gonna be anywhere close to what you can get with plain integers.
Yet another problem is when reading the file. Creating a new string each line is wasteful. Create the string once, call
reserve
on it with the anticipated number of characters and then reuse it. That one is small when compared to IO overhead, but it's good to get in habit of not neglecting small gains. -
wrote on 5 Jul 2015, 10:06 last edited by
Today I tried to see why the execution time was different in the test application versus real application, I found an error in the code of the test application. Once the error was corrected, the time in the application of test and the real one are equal, but comparing times with java, thus are very different. Java uses 350 milliseconds but in QT, the time is 1964 milliseconds using Qstring and 1190 milliseconds using QByteArray.
I know the c ++ but in the last 15 years I have been programming in Java, where some things are different.
About your question , @Huulivoide , of why I store values as strings, it is because I need to have those values to operate them later, store, string or float does not matter because I operate both strings as float
I tried to pass and return by reference as you indicate and times have fallen to 1632 in the case of using QString and 1133 in the case of QByteArray, well above on java's time.
the getY () function that you indicate me, it is so as It is copied from java, java a null string is different than the empty string and you can not ask if a null string is empty with .isEmpty (), in QT I've noticed that .isEmpty () you can use it with a null string.
@Chris-Kawa, I will try what you have indicated me, the class stores the String because in subsequent processes I work with such data as strings, also as floats but I have more operations with strings than floats.
-
wrote on 6 Jul 2015, 08:25 last edited by nurtan 7 Jun 2015, 08:26
I've made the changes that you have told me, both using QString as QByteArray classes, times have improved compared to the first post, and using QByteArray has been faster than QString , so I have taken this option.
Coordenada.cpp
QByteArray &Coordenada2::getComentario() { return comentario; } void Coordenada2::setComentario(const QByteArray &comentario) { this->comentario = comentario; } Coordenada2::Coordenada2() { x=QByteArray("0"); y=QByteArray("0"); z=QByteArray("0"); mantisa=QByteArray(""); } QByteArray &Coordenada2::getMantisa(){ return mantisa; } QByteArray Coordenada2::getMantisa(const QByteArray &dato) { QByteArray mantisa= QByteArray(""); for (int x = 0; x < dato.length(); x++) { switch (dato.at(x)) { case 'G': case 'X': case 'Y': case 'Z': case 'M': case 'H': case 'R': case 'A': case 'I': case 'J': case 'K': case 'S': case 'T': case 'P': case 'F': mantisa.append(dato.at(x)); mantisa.append(""); } } return mantisa; } void Coordenada2::setMantisa(const QByteArray &datos) { mantisa = getMantisa(datos); } Coordenada2::Coordenada2(const QByteArray &lineacodigo) { procesaLinea(lineacodigo); setMantisa(lineacodigo); } bool Coordenada2::isX() { return !x.isEmpty(); } bool Coordenada2::isY() { return !y.isEmpty(); } bool Coordenada2::isZ() { return !z.isEmpty(); } QByteArray &Coordenada2::getX() { return x; } float Coordenada2::getNumeroX() { if (x.isEmpty()) { return 0; } return x.toFloat(); } void Coordenada2::setX(const QByteArray &x) { this->x = x; anadeMantisa("X"); } void Coordenada2::setX(const float &x) { this->x = QByteArray(reinterpret_cast<const char *>(&x), sizeof (x)); anadeMantisa("X"); } QByteArray &Coordenada2::getY() { return y; } float Coordenada2::getNumeroY() { if (y.isEmpty()) { return 0; } return y.toFloat(); } void Coordenada2::setY(const QByteArray &y) { this->y = y; anadeMantisa("Y"); } void Coordenada2::setY(const float &y) { this->y=QByteArray(reinterpret_cast<const char *>(&y), sizeof (y)); anadeMantisa("Y"); } QByteArray &Coordenada2::getZ() { return z; } float Coordenada2::getNumeroZ() { if (z.isEmpty()) { return 0; } return z.toFloat(); } void Coordenada2::setZ(const QByteArray &z) { this->z = z; anadeMantisa("Z"); } void Coordenada2::setZ(const float &z) { this->z = QByteArray(reinterpret_cast<const char *>(&z), sizeof (z)); anadeMantisa("Z"); } void Coordenada2::anadeMantisa(const QByteArray parametro) { if (mantisa.isEmpty()) { mantisa = parametro; } if (!mantisa.contains(parametro)) { mantisa.append(" "); mantisa.append(parametro); } } void Coordenada2::procesaLinea(const QByteArray &codigo) { int pos = 0; comentario = QByteArray(""); while (pos < codigo.length()) { char letra = codigo.at(pos++); if (pos >= codigo.length()) { if (letra == '/') { comentario = QByteArray("/"); } else { comentario.append(letra); } break; } switch (letra) { case 'X': if (!isNumeric(codigo.at(pos)) && codigo.at(pos) != '-' && codigo.at(pos) != '+') { comentario.append(letra); break; } x=QByteArray(""); while (pos < codigo.length() && (isNumeric(codigo.at(pos)) || codigo.at(pos) == '.' || codigo.at(pos) == '-' || codigo.at(pos) == '+')) { letra = codigo.at(pos++); x.append(letra); } break; case 'Y': if (!isNumeric(codigo.at(pos)) && codigo.at(pos) != '-' && codigo.at(pos) != '+') { comentario.append(letra); break; } y=QByteArray(""); while (pos < codigo.length() && (isNumeric(codigo.at(pos)) || codigo.at(pos) == '.' || codigo.at(pos) == '-' || codigo.at(pos) == '+')) { letra = codigo.at(pos++); y.append(letra); } break; case 'Z': if (!isNumeric(codigo.at(pos)) && codigo.at(pos) != '-' && codigo.at(pos) != '+') { comentario.append(letra); break; } z=QByteArray(""); while (pos < codigo.length() && (isNumeric(codigo.at(pos)) || codigo.at(pos) == '.' || codigo.at(pos) == '-' || codigo.at(pos) == '+')) { letra = codigo.at(pos++); z.append(letra); } break; default: if (letra != ' ') { do { comentario.append(letra); if (pos < codigo.length()) { letra = codigo.at(pos++); } } while (pos < codigo.length() && letra != ' '); if (pos <= codigo.length()) { comentario.append(letra); } } break; } } if (this->comentario.length() > 0 && !(this->comentario.at(0))=='/') { if (x.length()>0 || y.length()>0 || z.length() ) { if (comentario.length()>0) { this->comentario.clear(); } } } } bool Coordenada2::isComentario() { return !comentario.isEmpty(); } bool Coordenada2::isNull() { if (!this->isX() && !this->isY() && !this->isZ()) { return true; } else { return false; } } bool Coordenada2::isNumeric(const char &cadena) { if ('0' <= cadena && cadena <= '9' ) return true; else return false; }
main.cpp
void leeArchivo(){ QFile archivo("c:\\coordenadas.txt"); int lineas = 0; float maxX = -10000; float maxY = -10000; float minX = 10000; float minY = 10000; float maxZ = -10000; float minZ = 10000; bool hayX = false; bool hayY = false; Coordenada2 elementoAnterior(); if (archivo.open(QIODevice::ReadOnly | QIODevice::Text)){ QTextStream in(&archivo); QElapsedTimer tiempo; tiempo.start(); while (!in.atEnd()) { lineas++; Coordenada2 elemento(in.readLine().toLatin1()); if (maxX < elemento.getNumeroX()) { maxX = elemento.getNumeroX(); } if (minX > elemento.getNumeroX()) { minX = elemento.getNumeroX(); } if (maxY < elemento.getNumeroY()) { maxY = elemento.getNumeroY(); } if (minY > elemento.getNumeroY()) { minY = elemento.getNumeroY(); } if (maxZ < elemento.getNumeroZ()) { maxZ = elemento.getNumeroZ(); } if (minZ > elemento.getNumeroZ() ) { minZ = elemento.getNumeroZ(); } if (elemento.isX()) { hayX = true; } if (elemento.isY()) { hayY = true; } } qDebug()<<"Tiempo :"<<tiempo.elapsed(); qDebug()<<maxX; archivo.close(); } } int main(int argc, char *argv[]) { QApplication a(argc, argv); leeArchivo(); return 0; }
I've been testing to measure the creation times of Coordenada2 object for the first 50 lines of the file, and the result has been that Java was slower than QT creating each of the objects. But for the total of java linesis still much faster. I made a test by increasing the number of records and comparing the result in java and QT. The results are as follows:
java QT lines 1 0 10 3 0 100 8 1 300 13 2 1000 18 8 3000 25 27 10000 46 84 30000 104 295 100000 289 936 300000 349 1199 371723
As you can see java is much more lineal in time, every time we increase records, the time increases , however QT has some very good times to 10000 lines, but from there, the times are far higher than those of java. Any idea why his happens'
1/15