java - maximo - numero mayor y su posicion en c++
Java 8 veces más rápido con matrices que std:: vector en C++. ¿Qué hice mal? (4)
Aquí está la versión de C ++ con los datos por nodo reunidos en una estructura, y se utiliza un solo vector de esa estructura:
#include <vector>
#include <cmath>
#include <iostream>
class FloodIsolation {
public:
FloodIsolation() :
numberOfCells(20000),
data(numberOfCells)
{
}
~FloodIsolation(){
}
void isUpdateNeeded() {
for (int i = 0; i < numberOfCells; ++i) {
data[i].h = data[i].h + 1;
data[i].floodedCells = !data[i].floodedCells;
data[i].floodedCellsTimeInterval = !data[i].floodedCellsTimeInterval;
data[i].qInflow = data[i].qInflow + 1;
data[i].qStartTime = data[i].qStartTime + 1;
data[i].qEndTime = data[i].qEndTime + 1;
data[i].lowerFloorCells = data[i].lowerFloorCells + 1;
data[i].cellLocationX = data[i].cellLocationX + 1;
data[i].cellLocationY = data[i].cellLocationY + 1;
data[i].cellLocationZ = data[i].cellLocationZ + 1;
data[i].levelOfCell = data[i].levelOfCell + 1;
data[i].valueOfCellIds = data[i].valueOfCellIds + 1;
data[i].h0 = data[i].h0 + 1;
data[i].vU = data[i].vU + 1;
data[i].vV = data[i].vV + 1;
data[i].vUh = data[i].vUh + 1;
data[i].vVh = data[i].vVh + 1;
data[i].vUh0 = data[i].vUh0 + 1;
data[i].vVh0 = data[i].vVh0 + 1;
data[i].ghh = data[i].ghh + 1;
data[i].sfx = data[i].sfx + 1;
data[i].sfy = data[i].sfy + 1;
data[i].qIn = data[i].qIn + 1;
for(int j = 0; j < nEdges; ++j) {
data[i].flagInterface[j] = !data[i].flagInterface[j];
data[i].typeInterface[j] = data[i].typeInterface[j] + 1;
data[i].neighborIds[j] = data[i].neighborIds[j] + 1;
}
}
}
private:
const int numberOfCells;
static const int nEdges = 6;
struct data_t {
bool floodedCells = 0;
bool floodedCellsTimeInterval = 0;
double valueOfCellIds = 0;
double h = 0;
double h0 = 0;
double vU = 0;
double vV = 0;
double vUh = 0;
double vVh = 0;
double vUh0 = 0;
double vVh0 = 0;
double ghh = 0;
double sfx = 0;
double sfy = 0;
double qInflow = 0;
double qStartTime = 0;
double qEndTime = 0;
double qIn = 0;
double nx = 0;
double ny = 0;
double floorLevels = 0;
int lowerFloorCells = 0;
bool floorCompleteleyFilled = 0;
double cellLocationX = 0;
double cellLocationY = 0;
double cellLocationZ = 0;
int levelOfCell = 0;
bool flagInterface[nEdges] = {};
int typeInterface[nEdges] = {};
int neighborIds[nEdges] = {};
};
std::vector<data_t> data;
};
int main() {
std::ios_base::sync_with_stdio(false);
FloodIsolation isolation;
clock_t start = clock();
for (int i = 0; i < 400; ++i) {
if(i % 100 == 0) {
std::cout << i << "/n";
}
isolation.isUpdateNeeded();
}
clock_t stop = clock();
std::cout << "Time: " << difftime(stop, start) / 1000 << "/n";
}
Ahora es el doble de la velocidad de la versión Java. (846 contra 1631).
Lo más probable es que el JIT haya notado la quema de caché de acceso a datos en todo el lugar, y transformó su código en un orden lógicamente similar pero más eficiente.
También desactivé la sincronización stdio, ya que eso solo es necesario si mezclas
printf
/
scanf
con C ++
std::cout
y
std::cin
.
De hecho, solo imprime unos pocos valores, pero el comportamiento predeterminado de C ++ para imprimir es demasiado paranoico e ineficiente.
Si
nEdges
no es un valor constante real, entonces los 3 valores de "matriz" deberán eliminarse de la
struct
.
Eso no debería causar un gran impacto en el rendimiento.
Es posible que pueda obtener otro aumento de rendimiento clasificando los valores en esa
struct
disminuyendo el tamaño, reduciendo así la huella de la memoria (y clasificando el acceso también cuando no importa).
Pero no estoy seguro.
Una regla general es que una sola falta de caché es 100 veces más costosa que una instrucción. Organizar sus datos para tener coherencia de caché tiene mucho valor.
Si reorganizar los datos en una
struct
es factible, puede cambiar su iteración para que esté sobre cada contenedor a su vez.
Además, tenga en cuenta que las versiones de Java y C ++ tenían algunas diferencias sutiles en ellas. La que vi fue que la versión de Java tiene 3 variables en el ciclo "para cada borde", mientras que la versión de C ++ solo tenía 2. Hice que la mía coincidiera con la de Java. No sé si hay otros.
Tengo el siguiente código Java con varias matrices grandes que nunca cambian su tamaño. Se ejecuta en 1100 ms en mi computadora.
Implementé el mismo código en C ++ y usé
std::vector
.
El tiempo de implementación de C ++ que ejecuta exactamente el mismo código es de 8800 ms en mi computadora. ¿Qué hice mal, para que funcione tan lentamente?
Básicamente, el código hace lo siguiente:
for (int i = 0; i < numberOfCells; ++i) {
h[i] = h[i] + 1;
floodedCells[i] = !floodedCells[i];
floodedCellsTimeInterval[i] = !floodedCellsTimeInterval[i];
qInflow[i] = qInflow[i] + 1;
}
Se itera a través de diferentes matrices con un tamaño de alrededor de 20000.
Puede encontrar ambas implementaciones en los siguientes enlaces:
- Java: https://ideone.com/R8KqjT
- C ++: https://ideone.com/Lu7RpE
(En ideone solo podía ejecutar el ciclo 400 veces en lugar de 2000 veces debido a la limitación de tiempo. Pero incluso aquí hay una diferencia de tres veces)
Como adivinó @Stefan en un comentario sobre la respuesta de @ CaptainGiraffe, ganas bastante usando un vector de estructuras en lugar de una estructura de vectores. El código corregido se ve así:
#include <vector>
#include <cmath>
#include <iostream>
#include <time.h>
class FloodIsolation {
public:
FloodIsolation() :
h(0),
floodedCells(0),
floodedCellsTimeInterval(0),
qInflow(0),
qStartTime(0),
qEndTime(0),
lowerFloorCells(0),
cellLocationX(0),
cellLocationY(0),
cellLocationZ(0),
levelOfCell(0),
valueOfCellIds(0),
h0(0),
vU(0),
vV(0),
vUh(0),
vVh(0),
vUh0(0),
vVh0(0),
ghh(0),
sfx(0),
sfy(0),
qIn(0),
typeInterface(nEdges, 0),
neighborIds(nEdges, 0)
{
}
~FloodIsolation(){
}
void Update() {
h = h + 1;
floodedCells = !floodedCells;
floodedCellsTimeInterval = !floodedCellsTimeInterval;
qInflow = qInflow + 1;
qStartTime = qStartTime + 1;
qEndTime = qEndTime + 1;
lowerFloorCells = lowerFloorCells + 1;
cellLocationX = cellLocationX + 1;
cellLocationY = cellLocationY + 1;
cellLocationZ = cellLocationZ + 1;
levelOfCell = levelOfCell + 1;
valueOfCellIds = valueOfCellIds + 1;
h0 = h0 + 1;
vU = vU + 1;
vV = vV + 1;
vUh = vUh + 1;
vVh = vVh + 1;
vUh0 = vUh0 + 1;
vVh0 = vVh0 + 1;
ghh = ghh + 1;
sfx = sfx + 1;
sfy = sfy + 1;
qIn = qIn + 1;
for(int j = 0; j < nEdges; ++j) {
++typeInterface[j];
++neighborIds[j];
}
}
private:
static const int nEdges = 6;
bool floodedCells;
bool floodedCellsTimeInterval;
std::vector<int> neighborIds;
double valueOfCellIds;
double h;
double h0;
double vU;
double vV;
double vUh;
double vVh;
double vUh0;
double vVh0;
double ghh;
double sfx;
double sfy;
double qInflow;
double qStartTime;
double qEndTime;
double qIn;
double nx;
double ny;
double floorLevels;
int lowerFloorCells;
bool flagInterface;
std::vector<int> typeInterface;
bool floorCompleteleyFilled;
double cellLocationX;
double cellLocationY;
double cellLocationZ;
int levelOfCell;
};
int main() {
std::vector<FloodIsolation> isolation(20000);
clock_t start = clock();
for (int i = 0; i < 400; ++i) {
if(i % 100 == 0) {
std::cout << i << "/n";
}
for (auto &f : isolation)
f.Update();
}
clock_t stop = clock();
std::cout << "Time: " << difftime(stop, start) / 1000 << "/n";
}
Compilado con el compilador de VC ++ 2015 CTP, usando
-EHsc -O2b2 -GL -Qpar
, obtengo resultados como:
0
100
200
300
Time: 0.135
Compilar con g ++ produce un resultado un poco más lento:
0
100
200
300
Time: 0.156
En el mismo hardware, usando el compilador / JVM de Java 8u45, obtengo resultados como:
0
100
200
300
Time: 181
Esto es aproximadamente un 35% más lento que la versión de VC ++, y aproximadamente un 16% más lento que la versión de g ++.
Si aumentamos el número de iteraciones al 2000 deseado, la diferencia se reduce a solo el 3%, lo que sugiere que parte de la ventaja de C ++ en este caso es simplemente una carga más rápida (un problema perenne con Java), no realmente en la ejecución misma. Esto no me parece sorprendente en este caso: el cálculo que se mide (en el código publicado) es tan trivial que dudo que la mayoría de los compiladores puedan hacer mucho para optimizarlo.
Sí, el caché en la versión c ++ toma un martilleo. Parece que el JIT está mejor equipado para manejar esto.
Si cambia el exterior
for
in isUpdateNeeded () a fragmentos más cortos.
La diferencia se va.
La siguiente muestra produce una aceleración 4x.
void isUpdateNeeded() {
for (int i = 0; i < numberOfCells; ++i) {
h[i] = h[i] + 1;
floodedCells[i] = !floodedCells[i];
floodedCellsTimeInterval[i] = !floodedCellsTimeInterval[i];
qInflow[i] = qInflow[i] + 1;
qStartTime[i] = qStartTime[i] + 1;
qEndTime[i] = qEndTime[i] + 1;
}
for (int i = 0; i < numberOfCells; ++i) {
lowerFloorCells[i] = lowerFloorCells[i] + 1;
cellLocationX[i] = cellLocationX[i] + 1;
cellLocationY[i] = cellLocationY[i] + 1;
cellLocationZ[i] = cellLocationZ[i] + 1;
levelOfCell[i] = levelOfCell[i] + 1;
valueOfCellIds[i] = valueOfCellIds[i] + 1;
h0[i] = h0[i] + 1;
vU[i] = vU[i] + 1;
vV[i] = vV[i] + 1;
vUh[i] = vUh[i] + 1;
vVh[i] = vVh[i] + 1;
}
for (int i = 0; i < numberOfCells; ++i) {
vUh0[i] = vUh0[i] + 1;
vVh0[i] = vVh0[i] + 1;
ghh[i] = ghh[i] + 1;
sfx[i] = sfx[i] + 1;
sfy[i] = sfy[i] + 1;
qIn[i] = qIn[i] + 1;
for(int j = 0; j < nEdges; ++j) {
neighborIds[i * nEdges + j] = neighborIds[i * nEdges + j] + 1;
}
for(int j = 0; j < nEdges; ++j) {
typeInterface[i * nEdges + j] = typeInterface[i * nEdges + j] + 1;
}
}
}
Esto muestra en un grado razonable que las fallas de caché son la razón de la desaceleración. También es importante tener en cuenta que las variables no son dependientes, por lo que se crea fácilmente una solución roscada.
Orden restaurada
Según el comentario de stefans, intenté agruparlos en una estructura usando los tamaños originales. Esto elimina la presión de caché inmediata de manera similar. El resultado es que la versión c ++ (CCFLAG -O3) es aproximadamente un 15% más rápida que la versión java.
Barnizado ni corto ni bonito.
#include <vector>
#include <cmath>
#include <iostream>
class FloodIsolation {
struct item{
char floodedCells;
char floodedCellsTimeInterval;
double valueOfCellIds;
double h;
double h0;
double vU;
double vV;
double vUh;
double vVh;
double vUh0;
double vVh0;
double sfx;
double sfy;
double qInflow;
double qStartTime;
double qEndTime;
double qIn;
double nx;
double ny;
double ghh;
double floorLevels;
int lowerFloorCells;
char flagInterface;
char floorCompletelyFilled;
double cellLocationX;
double cellLocationY;
double cellLocationZ;
int levelOfCell;
};
struct inner_item{
int typeInterface;
int neighborIds;
};
std::vector<inner_item> inner_data;
std::vector<item> data;
public:
FloodIsolation() :
numberOfCells(20000), inner_data(numberOfCells * nEdges), data(numberOfCells)
{
}
~FloodIsolation(){
}
void isUpdateNeeded() {
for (int i = 0; i < numberOfCells; ++i) {
data[i].h = data[i].h + 1;
data[i].floodedCells = !data[i].floodedCells;
data[i].floodedCellsTimeInterval = !data[i].floodedCellsTimeInterval;
data[i].qInflow = data[i].qInflow + 1;
data[i].qStartTime = data[i].qStartTime + 1;
data[i].qEndTime = data[i].qEndTime + 1;
data[i].lowerFloorCells = data[i].lowerFloorCells + 1;
data[i].cellLocationX = data[i].cellLocationX + 1;
data[i].cellLocationY = data[i].cellLocationY + 1;
data[i].cellLocationZ = data[i].cellLocationZ + 1;
data[i].levelOfCell = data[i].levelOfCell + 1;
data[i].valueOfCellIds = data[i].valueOfCellIds + 1;
data[i].h0 = data[i].h0 + 1;
data[i].vU = data[i].vU + 1;
data[i].vV = data[i].vV + 1;
data[i].vUh = data[i].vUh + 1;
data[i].vVh = data[i].vVh + 1;
data[i].vUh0 = data[i].vUh0 + 1;
data[i].vVh0 = data[i].vVh0 + 1;
data[i].ghh = data[i].ghh + 1;
data[i].sfx = data[i].sfx + 1;
data[i].sfy = data[i].sfy + 1;
data[i].qIn = data[i].qIn + 1;
for(int j = 0; j < nEdges; ++j) {
inner_data[i * nEdges + j].neighborIds = inner_data[i * nEdges + j].neighborIds + 1;
inner_data[i * nEdges + j].typeInterface = inner_data[i * nEdges + j].typeInterface + 1;
}
}
}
static const int nEdges;
private:
const int numberOfCells;
};
const int FloodIsolation::nEdges = 6;
int main() {
FloodIsolation isolation;
clock_t start = clock();
for (int i = 0; i < 4400; ++i) {
if(i % 100 == 0) {
std::cout << i << "/n";
}
isolation.isUpdateNeeded();
}
clock_t stop = clock();
std::cout << "Time: " << difftime(stop, start) / 1000 << "/n";
}
Mi resultado difiere ligeramente de Jerry Coffins para los tamaños originales. Para mí las diferencias persisten. Bien podría ser mi versión Java, 1.7.0_75.
Sospecho que se trata de la asignación de memoria.
Estoy pensando que
Java
toma un bloque contiguo grande al inicio del programa, mientras que
C++
le pide al sistema operativo fragmentos a medida que avanza.
Para poner a prueba esa teoría, hice una modificación en la versión de
C++
y de repente comenzó a ejecutarse un poco más rápido que la versión de
Java
:
int main() {
{
// grab a large chunk of contiguous memory and liberate it
std::vector<double> alloc(20000 * 20);
}
FloodIsolation isolation;
clock_t start = clock();
for (int i = 0; i < 400; ++i) {
if(i % 100 == 0) {
std::cout << i << "/n";
}
isolation.isUpdateNeeded();
}
clock_t stop = clock();
std::cout << "Time: " << (1000 * difftime(stop, start) / CLOCKS_PER_SEC) << "/n";
}
Tiempo de ejecución sin el vector de preasignación:
0
100
200
300
Time: 1250.31
Tiempo de ejecución con el vector de preasignación:
0
100
200
300
Time: 331.214
Runtime para
Java
versión
Java
:
0
100
200
300
Time: 407