Я новичок в программировании на C ++. Я пытаюсь увидеть преимущества от переноса всего моего программного обеспечения MatLab на C ++. Я делаю некоторые конечно-элементные вещи, в основном нелинейные, поэтому одна из операций, которые мне нужно выполнить в массовом порядке, — это перекрестное произведение двух векторов. Я протестировал две реализации в Matlab и C ++, C ++, кажется, намного быстрее. В C ++ две разные реализации дают разные тайминги. Я использую Intel MKL.
Вот код:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
#include <mkl.h>void vprod( double vgr[3], double vg1[3], double vg2[3]);int main() {
double v1[3]={1.22, 2.65, 3.65}, v2[3]={6.98, 98.159, 54.65}, vr[3];
int LC=1000000;
int i,j,k;
double tiempo=0.0, tinicial;
//------------------------------------------------------------------------
std::cout << "INLINE METHOD: " << std::endl;
tinicial = dsecnd();
for (i=0; i<LC; i++){
vr[0] = v1[1]*v2[2]-v1[2]*v2[1];
vr[1] =-(v1[0]*v2[2]-v1[2]*v2[0]);
vr[2] = v1[0]*v2[1]-v1[1]*v2[0];
};
tiempo = (dsecnd() - tinicial);
std::cout << "Tiempo Total: " << tiempo << std::endl;
std::cout << "Resultado: " << vr[0] << std::endl;
//------------------------------------------------------------------------
//------------------------------------------------------------------------
std::cout << "FUNCTION METHOD: " << std::endl;
tinicial = dsecnd();
for (i=0; i<LC; i++){
vprod (vr,v1,v2);
};
tiempo = (dsecnd() - tinicial);
std::cout << "Tiempo Total: " << tiempo << std::endl;
std::cout << "Resultado: " << vr[0] << std::endl;
//------------------------------------------------------------------------
std::cin.ignore();
return 0;
}inline void vprod( double vgr[3], double vg1[3], double vg2[3]){
vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
}
Мой вопрос: почему первая реализация в 3 раза быстрее, чем вторая? Это результат служебных вызовов? Спасибо !!!
РЕДАКТИРОВАТЬЯ изменил код, чтобы компилятор не «угадывал» результаты цикла с постоянными векторами. Как показал @phonetagger, результаты очень разные. У меня есть 28500 микросекунд без использования функции vprod и 29000 микросекунд с использованием vprod
функция. Это число было получено с помощью оптимизации Ox. Изменение оптимизации не влияет на сравнение, если включено ключевое слово inline, хотя цифры немного увеличиваются. Кроме того, если ключевое слово inline не используется (и оптимизация отключена), время составляет 32000 без использования функции vprod и 37000 с использованием этой функции. Таким образом, издержки вызова функции могут составлять около 5000 микросекунд.
Новый код:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
#include <mkl.h>
//#include <mkl_lapack.h>
void vprod( double *vgr, int ploc, double *vg1, double *vg2);int main() {
int nv=1000000;
int dim=3*nv;
double *v1, *v2, *vr; // Declare Pointers
int ploc, i;
double tiempo=0.0, tinicial;
v1 = new double [dim]; //Allocate block of memory
v2 = new double [dim];
vr = new double [dim];
// Fill vectors with something
for (i = 0; i < dim; i++) {
v1[i] =1.25 + (double)(i+1);
v2[i] =2.62+ 2*(double)(i+7);
}//------------------------------------------------------------------------
std::cout << "RUTINA CON CODIGO INLINE: \n" ;
tinicial = dsecnd();
ploc = 0; // ploc points to an intermediate location.
for (i=0; i<nv; i++){
vr[ploc] = v1[ploc+1]*v2[ploc+2]-v1[ploc+2]*v2[ploc+1];
vr[ploc+1] =-(v1[ploc]*v2[ploc+2]-v1[ploc+2]*v2[ploc]);
vr[ploc+2] = v1[ploc]*v2[ploc+1]-v1[ploc+1]*v2[ploc];
ploc +=3;
};
tiempo = (dsecnd() - tinicial);
std::cout << "Tiempo Total: " << tiempo << ".\n";
std::cout << "Resultado: " << vr[0] << ".\n";
delete v1,v2,vr;
v1 = new double [dim]; //Allocate block of memory
v2 = new double [dim];
vr = new double [dim];
//------------------------------------------------------------------------
//------------------------------------------------------------------------
std::cout << "RUTINA LLAMANDO A FUNCION: \n" ;
ploc=0;
tinicial = dsecnd();
for (i=0; i<nv; i++){
vprod ( vr, ploc, v1, v2);
ploc +=3;
};
tiempo = (dsecnd() - tinicial);
std::cout << "Tiempo Total: " << tiempo << ".\n";
std::cout << "Resultado: " << vr[0] << ".\n";
//------------------------------------------------------------------------
std::cin.ignore();
return 0;
}inline void vprod( double *vgr, int ploc, double *vg1, double *vg2) {
vgr[ploc] = vg1[ploc+1]*vg2[ploc+2]-vg1[ploc+2]*vg2[ploc+1];
vgr[ploc+1] = -(vg1[ploc]*vg2[ploc+2]-vg1[ploc+2]*vg2[ploc]);
vgr[ploc+2] = vg1[ploc]*vg2[ploc+1]-vg1[ploc+1]*vg2[ploc];
}
Мартин, ты абсолютно прав (см. Комментарий Мартина … 3-й комментарий под моим ответом от 5 октября 2012 г., 17:57). Да, кажется, что на более высоких уровнях оптимизации компилятор позволил себе понять, что он знает входящие значения ваших массивов, чтобы он мог выполнять все вычисления, цикл и все во время компиляции и полностью оптимизировать цикл.
Я перекодировал тестовый код в три отдельных файла (один заголовок & два исходных файла) и сломал вычисление & зацикливайтесь на отдельной функции, чтобы компилятор не был слишком умным со своими оптимизациями. Теперь он не может оптимизировать циклы в вычисления во время компиляции. Ниже приведены мои новые результаты. Обратите внимание, что я добавил еще один цикл (от 0 до 50) вокруг исходного цикла от 0 до 1000000, а затем разделил его на 50. Я сделал это по двум причинам: он позволяет нам сравнивать сегодняшние цифры с предыдущими, а также усредняет ошибки из-за перестановки процессов в середине теста. Это может не иметь значения для вас, так как я думаю, что dsecnd () сообщает только о времени процессора своего конкретного процесса?
Во всяком случае, вот мои новые результаты …….
(И да, нечетный результат «встроенного ключевого слова, оптимизация -O1» быстрее, чем -O2 или -O3, повторяется, как и странность «нет встроенного ключевого слова, оптимизация -O1». Я не копался в сборке чтобы понять, почему это может быть.)
//========================================================================================
// File: so.h
void loop_inline( const int LC, double vgr[3], double vg1[3], double vg2[3]);
void loop_func( const int LC, double vgr[3], double vg1[3], double vg2[3]);
//---------------------------------
// Comment or uncomment to test both ways...
#define USE_INLINE_KEYWORD
//
// Using g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-52) on an x86 machine...
//
// microseconds microseconds
// "hardcoded inline" "via vprod() function"// [i]=inlined, [-]=not
// ------------------ ----------------------
// inline keyword
// no optimization 11734 14598 [-]
// optimization -O1 4617 4616 [i]
// optimization -O2 7754 7838 [i]
// optimization -O3 7777 7673 [i]
//
// no inline keyword
// no optimization 11807 14602 [-]
// optimization -O1 4651 7691 [-]
// optimization -O2 7755 7383 [-]
// optimization -O3 7921 7432 [-]
//
// Note that in all cases, both results were reported as -213.458.
//
/* My cut & paste "build & test script" to run on the Linux command prompt...
echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""rm -f a.out; g++ -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "No optimization:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O1 -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "Optimization -O1:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O2 -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "Optimization -O2:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O3 -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "Optimization -O3:---------------"; objdump -d a.out | grep call | grep vprod; a.out
...if the "objdump -d a.out | grep call | grep vprod" command returns something
like "call 8048754 <_Z5vprodPdS_S_>", then I know that the call to vprod() is
NOT inlined, whereas if it returns nothing, I know the call WAS inlined.
*/
//========================================================================================
// File: so.cpp
// Sorry so messy, I didn't bother to clean up the #includes.......
#include <stdint.h>
#include <inttypes.h>
#include <stddef.h> // for NULL
#include <stdlib.h> // for exit()
#include <stdio.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
//#include <mkl.h>
#include "so.h"
// My standin for dsecnd() since I don't have "mkl.h"...
#include <sys/time.h>
double dsecnd()
{
struct timeval tv;
if (gettimeofday(&tv,NULL))
{
fprintf(stderr,"\ngettimeofday() error\n\n");
exit(1);
}
return tv.tv_sec*1000000 + tv.tv_usec; // ...returns MICROSECONDS
//return tv.tv_sec + ((double)tv.tv_usec)/1000000; // ...returns SECONDS
}
//---------------------------------
#ifndef USE_INLINE_KEYWORD
// We're NOT using the 'inline' keyword, so define vprod() in this
// file so it can't possibly be inlined where it's called (in the
// other source file).
void vprod( double vgr[3], double vg1[3], double vg2[3]){
//void vprod( double *vgr, double *vg1, double *vg2){
vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
}
#endif
int main() {
double v1[3]={1.22, 2.65, 3.65}, v2[3]={6.98, 98.159, 54.65}, vr[3];
int LC=1000000L;
int i, N=100;
double tiempo=0.0, tinicial;
//------------------------------------------------------------------------
std::cout << "INLINE METHOD: " << std::endl;
tinicial = dsecnd();
for (i=0; i<N; ++i)
loop_inline(LC,vr,v1,v2);
tiempo = (dsecnd() - tinicial)/N;
std::cout << "Tiempo Total: " << tiempo << std::endl;
std::cout << "Resultado: " << vr[0] << std::endl;
//------------------------------------------------------------------------
//------------------------------------------------------------------------
std::cout << "FUNCTION METHOD: " << std::endl;
tinicial = dsecnd();
for (i=0; i<N; ++i)
loop_func(LC,vr,v1,v2);
tiempo = (dsecnd() - tinicial)/N;
std::cout << "Tiempo Total: " << tiempo << std::endl;
std::cout << "Resultado: " << vr[0] << std::endl;
//------------------------------------------------------------------------
// std::cin.ignore();
return 0;
}
//========================================================================================
// File: so2.cpp
#include "so.h"
#ifdef USE_INLINE_KEYWORD
inline void vprod( double vgr[3], double vg1[3], double vg2[3]){
//void vprod( double *vgr, double *vg1, double *vg2){
vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
}
#else
// Not using 'inline' keyword, so just declare (prototype) the
// function here and define it in the other source file (so it
// can't possibly be inlined).
void vprod( double vgr[3], double vg1[3], double vg2[3]);
#endif
void loop_inline( const int LC, double vgr[3], double vg1[3], double vg2[3]){
for (int i=0; i<LC; i++) {
vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
}
}
void loop_func( const int LC, double vgr[3], double vg1[3], double vg2[3]){
for (int i=0; i<LC; i++) {
vprod (vgr,vg1,vg2);
}
}
Я не знаю, какой компилятор вы используете («MKL» — это набор компиляторов?), Но независимо от того, какой компилятор вы используете, уровни оптимизации будут оказывать существенное влияние на производительность вашего кода, иногда на несколько порядков. величина, в зависимости от вашего стиля кодирования & пытаетесь ли вы «поиграть», чтобы ваш код работал быстрее. Часто (хотя и не всегда) лучше позволить компилятору выполнять за вас хитрости, а вы просто сосредотачиваетесь на написании эффективных алгоритмов, а не на хитрости кодирования.
В любом случае, я запускал ваш код в своей системе различными способами, результаты которого приведены в комментариях к коду ниже …
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
//#include <mkl.h>
// My standin for dsecnd() since I don't have "mkl.h"...
#include <sys/time.h>
double dsecnd()
{
struct timeval tv;
if (gettimeofday(&tv,NULL))
{
fprintf(stderr,"\ngettimeofday() error\n\n");
exit(1);
}
return tv.tv_sec*1000000 + tv.tv_usec; // ...returns MICROSECONDS
//return tv.tv_sec + ((double)tv.tv_usec)/1000000; // ...returns SECONDS
}
//---------------------------------
// Uncomment one or both of these to test variations....
//#define USE_INLINE_KEYWORD
//#define DEFINE_vprod_AT_TOP
//
// Using g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-52) on an x86 machine...
//
// microseconds microseconds
// "hardcoded inline" "via vprod() function"// [i]=inlined, [-]=not
// ------------------ ----------------------
// inline keyword, at top
// no optimization 9501 17797 [-]
// optimization -O1 2 (see NOTE) 1 [i]
// optimization -O2 1 1 [i]
// optimization -O3 0 0 [i]
//
// no inline keyword, at top
// no optimization 9630 18203 [-]
// optimization -O1 1257 10681 [-]
// optimization -O2 1272 10694 [-]
// optimization -O3 0 1 [i]
//
// inline keyword, at bottom
// no optimization 9763 18333 [-]
// optimization -O1 1 0 [i]
// optimization -O2 2 1 [i]
// optimization -O3 0 0 [i]
//
// no inline keyword, at bottom
// no optimization 9900 18387 [-]
// optimization -O1 1289 10714 [-]
// optimization -O2 795 6740 [-]
// optimization -O3 1 0 [i]
//
// Note that in all cases, both results were reported as -213.458.
//
// NOTE: Especially since I'm using gettimeofday() instead of something
// that returns process (CPU) time, all results may include some
// time that the CPU spent processing other stuff, but even if
// that weren't the case (i.e. even if I used a function that
// returned only CPU time spent on this particular process), there
// would still be the quantization error of +/-1 microsecond on
// each end of the interval, meaning +/-2 microseconds overall.
//
/* My cut & paste "build & test script" to run on the Linux command prompt...
echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""rm -f a.out; g++ so.cpp
echo ""; echo "No optimization:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O1 so.cpp
echo ""; echo "Optimization -O1:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O2 so.cpp
echo ""; echo "Optimization -O2:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O3 so.cpp
echo ""; echo "Optimization -O3:---------------"; objdump -d a.out | grep call | grep vprod; a.out
...if the "objdump -d a.out | grep call | grep vprod" command returns something
like "call 8048754 <_Z5vprodPdS_S_>", then I know that the call to vprod() is
NOT inlined, whereas if it returns nothing, I know the call WAS inlined. There
is only one caller of vprod(), so the results can't be confusing.
*/
//
//---------------------------------
#ifdef DEFINE_vprod_AT_TOP
#ifdef USE_INLINE_KEYWORD
inline
#endif
void vprod( double vgr[3], double vg1[3], double vg2[3]){
//void vprod( double *vgr, double *vg1, double *vg2){
vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
}
#else
// Declare (prototype) the function only if NOT defining it at the top...
void vprod( double vgr[3], double vg1[3], double vg2[3]);
#endifint main() {
double v1[3]={1.22, 2.65, 3.65}, v2[3]={6.98, 98.159, 54.65}, vr[3];
int LC=1000000L;
int i,j,k;
double tiempo=0.0, tinicial;
//------------------------------------------------------------------------
std::cout << "INLINE METHOD: " << std::endl;
tinicial = dsecnd();
for (i=0; i<LC; i++){
vr[0] = v1[1]*v2[2]-v1[2]*v2[1];
vr[1] =-(v1[0]*v2[2]-v1[2]*v2[0]);
vr[2] = v1[0]*v2[1]-v1[1]*v2[0];
};
tiempo = (dsecnd() - tinicial);
std::cout << "Tiempo Total: " << tiempo << std::endl;
std::cout << "Resultado: " << vr[0] << std::endl;
//------------------------------------------------------------------------
//------------------------------------------------------------------------
std::cout << "FUNCTION METHOD: " << std::endl;
tinicial = dsecnd();
for (i=0; i<LC; i++){
vprod (vr,v1,v2);
};
tiempo = (dsecnd() - tinicial);
std::cout << "Tiempo Total: " << tiempo << std::endl;
std::cout << "Resultado: " << vr[0] << std::endl;
//------------------------------------------------------------------------
// std::cin.ignore();
return 0;
}#ifndef DEFINE_vprod_AT_TOP
#ifdef USE_INLINE_KEYWORD
inline
#endif
void vprod( double vgr[3], double vg1[3], double vg2[3]){
//void vprod( double *vgr, double *vg1, double *vg2){
vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
}
#endif
Теперь приемы кодирования, используемые компилятором, не достигают линейного уровня с увеличением уровней оптимизации; Трюки, которые воспроизводит компилятор, включаются на разных уровнях оптимизации и могут зависеть от того, используете ли вы ключевое слово «inline». Могут быть (и мои результаты указывают на то, что существуют) разные типы оптимизаций, которые может использовать компилятор, кроме встраивания функции. Интересно отметить, что, как я прочитал, ключевое слово «inline» на самом деле является просто подсказкой для компилятора, что вы хотите, чтобы функция была встроенной, и, вероятно, просто настраивает некоторый порог для определения, встроить ли функцию, которую она могла бы встроить. в любом случае, если оптимизация включена. Похоже, что при отключенной оптимизации функция вообще не была встроена, даже если используется ключевое слово «inline». Также интересно отметить, что определение prod () над main () или ниже main (), по-видимому, не имеет значения, является ли функция встроенной или нет.