J'ai toujours entendu dire que C++ était bien plus efficace que Java (et c'est pourquoi la plupart des jeux sont développés en C++).
J'ai écrit un petit algorithme pour résoudre le "casse-tête des huit reines" dans les deux Java et C++, en utilisant exactement le même algorithme, puis j'ai commencé à augmenter le nombre ou les carrés. En atteignant les cases de 20 * 20 ou même 22 * 22, il apparaît Java est beaucoup plus efficace (3 secondes vs 66 secondes pour C++).
Je ne sais pas pourquoi, mais je commence assez bien avec C++, il est donc possible que j'ai fait d'énormes erreurs de performances, donc je serai ravi d'accepter toute information qui pourrait m'aider à comprendre ce qui se passe.
Voici le code que j'utilise en Java:
import Java.awt.Point;
import Java.util.ArrayList;
import Java.util.List;
public class HuitDames {
/**
* La liste des coordnnées des dames.
*/
private static List<Point> positions = new ArrayList<>();
/**
* Largeur de la grille.
*/
private static final int LARGEUR_GRILLE = 22;
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
int i = 1;
placerDame(i);
for (Point point : positions) {
System.out.println("(" + point.x + "; " + point.y + ")");
}
}
/**
* Place une dame et return true si la position est bonne.
* @param i le numéro de la dame.
* @return si la position est bonne.
*/
private static boolean placerDame(int i) {
boolean bonnePosition = false;
for (int j = 1; j <= LARGEUR_GRILLE && bonnePosition == false; j++) {
Point emplacement = new Point(i, j);
positions.add(emplacement);
if (verifierPrise(emplacement) && (i == LARGEUR_GRILLE || placerDame(i + 1))) {
bonnePosition = true;
}
else {
positions.remove(i - 1);
}
}
return bonnePosition;
}
/**
* Vérifie que la nouvelle position n'est pas en prise avec une position déjà présente.
* @param position la position de la nouvelle dame.
* @return Si la position convient par rapport aux positions des autres dames.
*/
private static boolean verifierPrise(Point position) {
boolean nonPrise = true;
for (Point point : positions) {
if (!point.equals(position)) {
// Cas où sur la même colonne.
if (position.y == point.y) {
nonPrise = false;
}
// Cas où sur même diagonale.
if (Math.abs(position.y - point.y) == Math.abs(position.x - point.x)) {
nonPrise = false;
}
}
}
return nonPrise;
}
}
Et ci-dessous est le code en C++:
#include <iostream>
#include <list>
#include <math.h>
#include <stdlib.h>
using namespace std;
// Class to represent points.
class Point {
private:
double xval, yval;
public:
// Constructor uses default arguments to allow calling with zero, one,
// or two values.
Point(double x = 0.0, double y = 0.0) {
xval = x;
yval = y;
}
// Extractors.
double x() { return xval; }
double y() { return yval; }
};
#define LARGEUR_GRILLE 22
list<Point> positions;
bool verifierNonPrise(Point emplacement) {
bool nonPrise = true;
for (list<Point>::iterator it = positions.begin(); it!= positions.end(); it++) {
if (it->x() != emplacement.x()) {
if (it->y() == emplacement.y()) {
nonPrise = false;
}
if (abs(it->y() - emplacement.y()) == abs(it->x() - emplacement.x())) {
nonPrise = false;
}
}
}
return nonPrise;
}
bool placerDame(int i) {
bool bonnePosition = false;
for (int j = 1; j <= LARGEUR_GRILLE && !bonnePosition; j++) {
Point emplacement(i,j);
positions.Push_back(emplacement);
if (verifierNonPrise(emplacement) && (i == LARGEUR_GRILLE || placerDame(i + 1))) {
bonnePosition = true;
}
else {
positions.pop_back();
}
}
return bonnePosition;
}
int main()
{
int i = 1;
placerDame(i);
for (list<Point>::iterator it = positions.begin(); it!= positions.end(); it++) {
cout << "(" << it->x() << "; " << it->y() << ")" << endl;
}
return 0;
}
std::list
en C++ est une liste chaînée, tandis que Java.util.ArrayList
est un tableau. Essayez de remplacer std::list
par std::vector
. Assurez-vous également de compiler avec l'optimisation activée.
std::find_if
verifierNonPrise
retour anticipé> javac HuitDames.Java
> time Java HuitDames
real 0m0.368s
user 0m0.436s
sys 0m0.042s
> g++ -O3 -std=c++11 HuitDames.cpp
> time ./a.out
real 0m0.541s
user 0m0.539s
sys 0m0.002s
#include <iostream>
#include <vector>
#include <cmath>
#include <stdlib.h>
#include <chrono>
#include <algorithm>
using namespace std;
typedef std::pair<int, int> Point;
#define LARGEUR_GRILLE 22
vector<Point> positions;
bool verifierNonPrise(Point const& emplacement) {
return std::find_if(positions.begin(), positions.end(), [&emplacement](Point const& val){
if (val.first != emplacement.first) {
if ((val.second == emplacement.second) || (abs(val.second - emplacement.second) == abs(val.first - emplacement.first))) {
return true;
}
}
return false;
}) == positions.end();
}
bool placerDame(int i) {
bool bonnePosition = false;
for (int j = 1; j <= LARGEUR_GRILLE && !bonnePosition; j++) {
Point emplacement(i,j);
positions.Push_back(emplacement);
if (verifierNonPrise(emplacement) && (i == LARGEUR_GRILLE || placerDame(i + 1))) {
bonnePosition = true;
}
else {
positions.pop_back();
}
}
return bonnePosition;
}
int main()
{
using std::chrono::system_clock;
system_clock::time_point begin_time = system_clock::now();
int i = 1;
placerDame(i);
for (vector<Point>::iterator it = positions.begin(); it!= positions.end(); it++) {
cout << "(" << it->first << "; " << it->second << ")" << endl;
}
system_clock::time_point end_time = system_clock::now();
long long elapsed_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - begin_time).count();
cout << "Duration (milliseconds): "
<< elapsed_milliseconds
<< std::endl;
}
Testez cette version, mise à jour à l'aide des fonctionnalités C++ 11. Testé dans GCC 4.9.0 avec -std=c++11
. Testé sur Celeron 1,6 GHz, 512 Mo de RAM.
Temps sur mon PC:
Original: Durée (millisecondes): 12658
Première version: Durée (millisecondes): 3616
Version optimisée: Durée (millisecondes): 1745
Les changements sont:
vector
au lieu de list
Benchmark , et Words from Stroustrup .La source:
#include <iostream>
#include <vector>
#include <chrono>
#include <iomanip>
using namespace std;
typedef std::pair<int, int> Point;
#define LARGEUR_GRILLE 22
vector<Point> positions;
bool verifierNonPrise(const Point& emplacement) {
bool nonPrise = true;
for (const auto& p : positions) {
if (p.first != emplacement.first) {
if (p.second == emplacement.second) {
nonPrise = false;
}
if (abs(p.second - emplacement.second) ==
abs(p.first - emplacement.first)) {
nonPrise = false;
}
}
}
return nonPrise;
}
bool placerDame(int i) {
bool bonnePosition = false;
for (int j = 1; j <= LARGEUR_GRILLE && !bonnePosition; j++) {
Point emplacement(i, j);
positions.emplace_back(emplacement);
if (verifierNonPrise(emplacement) &&
(i == LARGEUR_GRILLE || placerDame(i + 1))) {
bonnePosition = true;
} else {
positions.pop_back();
}
}
return bonnePosition;
}
int main(int argc, char* argv[]) {
std::chrono::system_clock::time_point begin_time =
std::chrono::system_clock::now();
positions.reserve(LARGEUR_GRILLE);
placerDame(1);
for (const auto& p : positions) {
cout << "(" << p.first << "; " << p.second << ")" << endl;
}
std::chrono::system_clock::time_point end_time =
std::chrono::system_clock::now();
long long elapsed_milliseconds =
std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - begin_time).count();
std::cout << "Duration (milliseconds): " << elapsed_milliseconds
<< std::endl;
return 0;
}
Quelques changements plus profonds.
Les changements incluent:
Source (certaines recommandations mises à jour):
#include <algorithm>
#include <iostream>
#include <vector>
#include <chrono>
#include <iomanip>
using namespace std;
struct Point {
int x, y;
};
#define LARGEUR_GRILLE 22
vector<Point> positions;
bool verifierNonPrise(const Point& emplacement) {
return find_if(positions.cbegin(), positions.cend(), [&emplacement](const Point& p) {
return (p.x != emplacement.x &&
(p.y == emplacement.y ||
abs(p.y - emplacement.y) == abs(p.x - emplacement.x)));
}) == positions.cend();
}
bool placerDame(int i) {
for (int j = 1; j <= LARGEUR_GRILLE; j++) {
Point emplacement{i, j};
positions.Push_back(emplacement);
if (verifierNonPrise(emplacement) &&
(i == LARGEUR_GRILLE || placerDame(i + 1))) {
return true;
} else {
positions.pop_back();
}
}
return false;
}
int main(int argc, char* argv[]) {
std::chrono::system_clock::time_point begin_time =
std::chrono::system_clock::now();
positions.reserve(LARGEUR_GRILLE);
placerDame(1);
for (const auto& p : positions) {
cout << "(" << p.x << "; " << p.y << ")" << endl;
}
std::chrono::system_clock::time_point end_time =
std::chrono::system_clock::now();
long long elapsed_milliseconds =
std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - begin_time).count();
std::cout << "Duration (milliseconds): " << elapsed_milliseconds
<< std::endl;
return 0;
}
Comparer un langage géré et compilé dynamiquement comme Java à un langage compilé statiquement comme C++ est très difficile.
Vous comparerez toujours les pommes aux oranges dans un sens, car elles sont conceptuellement très différentes. Cela commence par l'utilisation des bibliothèques standard (ArrayList vs std :: list/vector) qui auront des caractéristiques de performances potentiellement très différentes, même votre code semble similaire dans les deux langues.
Ensuite, il y a le problème inhérent aux microbenchmarks dans Java (test court dans Java sont toujours plus lents parce que le JIT observera le déroulement du programme avant de décider quoi et comment il doit être compilé. Il en va de même pour les options du compilateur pour C++, même la structure du code source (classes compilées et liées indépendamment par rapport à une source de fichier unique) peut faire une différence significative (car il modifie la quantité de "perspicacité" du compilateur C++ dans les autres classes).
Vient ensuite la différence générale entre la gestion de la mémoire, la collecte des ordures et la gestion manuelle de la mémoire (les pointeurs intelligents, etc. sont toujours considérés comme une gestion manuelle de la mémoire).
Sans parler des différences générales de langage comme vous devez déclarer explicitement une méthode virtuelle en C++, tandis qu'en Java tous les La méthode membre est virtuelle par défaut (déterminer si elle est vraiment virtuelle au moment de l'exécution est laissé à la machine virtuelle).
Avec toutes ces différences, il y aura toujours des cas où une langue aura un avantage énorme sur l'autre. Un test simple avec une portée très limitée (comme votre test ici) en dit très peu sur chaque langue dans son ensemble.
Un autre point que les gens ont souvent tendance à ignorer est: comment pouvez-vous être productif avec une langue - la vitesse n'est pas tout (regardez à quel point les langages de script sont réussis dans certains domaines, bien qu'ils ne soient guère compétitifs en regardant uniquement à la vitesse d’excution). Le manque de performances peut être paralysant, mais la productivité peut aussi être faible.
Je peux battre un cheval mort ici, mais simplement faire une traduction ligne par ligne du Java en C++, sans même utiliser les paramètres de référence const ou autre chose, vous pouvez voir que le C++ est presque deux fois plus vite que Java. Toutes les mises en place "d'optimisation syntaxique" etc. ont peu d'effet le cas échéant ...
rep ~/Documents $ g++ -O3 Queen.cpp
rep ~/Documents $ javac Queen.Java
rep ~/Documents $ time Java Queen
(1; 1)
(2; 3)
(3; 5)
(4; 2)
(5; 4)
(6; 10)
(7; 14)
(8; 17)
(9; 20)
(10; 13)
(11; 19)
(12; 22)
(13; 18)
(14; 8)
(15; 21)
(16; 12)
(17; 9)
(18; 6)
(19; 16)
(20; 7)
(21; 11)
(22; 15)
real 0m4.806s
user 0m4.857s
sys 0m0.067s
rep ~/Documents $ time ./a.out
(1; 1)
(2; 3)
(3; 5)
(4; 2)
(5; 4)
(6; 10)
(7; 14)
(8; 17)
(9; 20)
(10; 13)
(11; 19)
(12; 22)
(13; 18)
(14; 8)
(15; 21)
(16; 12)
(17; 9)
(18; 6)
(19; 16)
(20; 7)
(21; 11)
(22; 15)
real 0m2.131s
user 0m2.113s
sys 0m0.000s
rep ~/Documents $
Queen.Java (traduit en anglais)
import Java.awt.Point;
import Java.util.ArrayList;
import Java.util.List;
public class Queen {
private static List<Point> positions = new ArrayList<>();
private static final int GRID_SIZE = 22;
public static void main(String[] args)
{
int i = 1;
placeQueen(i);
for (Point point : positions)
{
System.out.println("(" + point.x + "; " + point.y + ")");
}
}
private static boolean placeQueen(int i)
{
boolean bIsGoodPos = false;
for (int j = 1; j <= GRID_SIZE && bIsGoodPos == false; j++)
{
Point emplacement = new Point(i, j);
positions.add(emplacement);
if (verifyPos(emplacement) && (i == GRID_SIZE || placeQueen(i + 1)))
{
bIsGoodPos = true;
}
else
{
positions.remove(i - 1);
}
}
return bIsGoodPos;
}
private static boolean verifyPos(Point position)
{
boolean bIsSafe = true;
for (Point point : positions)
{
if (!point.equals(position))
{
if (position.y == point.y)
{
bIsSafe = false;
}
if (Math.abs(position.y - point.y) == Math.abs(position.x - point.x))
{
bIsSafe = false;
}
}
}
return bIsSafe;
}
}
Queen.cpp
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
struct Point
{
int x, y;
Point(int ii, int jj):x(ii), y(jj){}
};
vector<Point> positions;
int GRID_SIZE = 22;
bool verifyPos(Point position)
{
bool bIsSafe = true;
for(int i = 0; i < positions.size(); ++i)
{
Point point = positions[i];
if(point.x != position.x || point.y != position.y)
{
if(position.y == point.y)
{
bIsSafe = false;
}
if(abs(position.y - point.y) == abs(position.x - point.x))
{
bIsSafe = false;
}
}
}
return bIsSafe;
}
bool placeQueen(int i)
{
bool bIsGoodPos = false;
for(int j = 1; j <= GRID_SIZE && bIsGoodPos == false; j++)
{
Point p(i, j);
positions.Push_back(p);
if(verifyPos(p) && (i == GRID_SIZE || placeQueen(i + 1)))
{
bIsGoodPos = true;
}
else
{
positions.pop_back();
}
}
return bIsGoodPos;
}
int main(void)
{
int i = 1;
placeQueen(i);
for(int i = 0; i < positions.size(); ++i)
{
Point p = positions[i];
cout << "(" << p.x << "; " << p.y << ")" << endl;
}
return 0;
}
C++ peut le faire en 21 ms (sur un ancien core i7-860) si vous utilisez des bitmaps. Pour le chronométrage, j'ai commenté l'appel showSoln () puisqu'un affichage graphique de l'échiquier prend deux fois plus de temps que de trouver la solution.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <omp.h> //omp_get_wtime() is my favorite time function
using namespace std;
static const unsigned n(22); //size of board
static_assert(n<32,"use long unsigned for bit masks if n > 32");
static const unsigned mask((1U<<n)-1); //n wide bitmask with every bit set
void showSoln(unsigned* selCol, unsigned numSoln) { //show a solution
cout << "\nsolution " << numSoln << '\n';
for (unsigned row=0; row<n; ++row) {
for (unsigned col=0; col<n; ++col)
cout << (col==selCol[row]? " Q": " .");
cout << '\n';
}
}
void main() {
//for each row bitmasks that show what columns are attacked, 1 bit means attacked
unsigned ulAttack[n]; //cols attacked from upper left, shift right for next row
unsigned upAttack[n]; //cols attacked from straight up, same for next row
unsigned urAttack[n]; //cols attacked from upper right, shift left for next row
unsigned allAttack[n]; //OR of all attacks on given row
allAttack[0]= ulAttack[0]= upAttack[0]= urAttack[0]= 0; //no attacks on row 0
unsigned row= 0; //the row where now placing a queen
unsigned selCol[n]; //for each row the selected column
unsigned numSoln= 0; //count of soutions found
double wtime= omp_get_wtime();
for (;;) { //loop until find 1st (or all) solutions
if (allAttack[row]!=mask) { //if 'row' has a column not attacked
unsigned long bit;
_BitScanForward(&bit,~allAttack[row]); //find lowest column not attacked
//note - your compiler may have a different intrinsic for find lowest set bit
selCol[row]= bit; //remember selected column for this row
unsigned move= 1U<<bit; //convert selected column to bitmask
allAttack[row]|= move; //mark column attacked to prevent re-use
if (row==n-1) { //if move in last row have a soln
++numSoln;
showSoln(selCol,numSoln);
break; //remove this break if want all solutions
} else { //no solution yet, fill in rows below
unsigned nrow= row+1; //next row
//from attacks on this row plus 'move' decide attacks on row below
ulAttack[nrow]= (ulAttack[row] | move) >> 1;
upAttack[nrow]= (upAttack[row] | move);
urAttack[nrow]= ((urAttack[row] | move) << 1) & mask;
allAttack[nrow]= ulAttack[nrow] | upAttack[nrow] | urAttack[nrow];
row= nrow; //go to next row
}
} else { //else move on 'row' is impossible so backtrack
if (!row) //if backtrack from row 0 have found all solutions
break;
--row; //try next move in prior row
}
}
wtime= omp_get_wtime() - wtime;
cout << "numSoln= " << numSoln << '\n';
cout << "time= " << wtime*1000 << " msec\n";
}
De plus, il n'y a aucune raison d'utiliser des types float/doouble pour les coordonnées.
Vous devriez gagner en performances si vous ne forcez pas l'appel de bibliothèque abs à virgule flottante dans votre C++
Java stocke les coordonnées Point sous forme d'entier. Les fonctions get renvoient le double, mais c'est probablement plus facile à optimiser loin en Java, puis en c ++.
Il semble que pour le code qui ne nécessite pas trop d'accès à la mémoire et un calcul intensif, la différence entre Java un C++ est faible. Mais dans la situation opposée, la différence semble incroyable:
test.Java
import Java.lang.String;
import Java.util.ArrayList;
import Java.util.List;
import Java.util.Random;
import Java.util.Scanner;
public class test{
private static Random gna=new Random();
public static double new_value(double value){
if (value<0.5) value=0;
return value*value;
}
public static void main(String[] args) {
long start_time = System.currentTimeMillis();
List<Double> ze_list=new ArrayList();
for (int i=0;i<1e8;i++){
double temp=new_value(gna.nextDouble());
ze_list.add(temp);
}
long end_time = System.currentTimeMillis();
System.out.println("Time (s) :"+ ((end_time-start_time)/1000));
Scanner input = new Scanner(System.in);
String inputval = input.next();
}
}
et le comparer à test.cpp:
#include <iostream>
#include <vector>
#include <ctime>
#include <random>
using namespace std;
static default_random_engine dre1(time(0));
static uniform_real_distribution <double> urd1(0, 1);
static double new_value(double value){
if (value<0.5) value=0;
return value*value;
}
int main(void){
time_t tbegin,tend;
double texec=0;
tbegin=time(NULL);
vector<double> ze_list;
for (int i=0;i<1e8;i++){
double temp=new_value(urd1(dre1));
ze_list.Push_back(temp);
}
tend=time(NULL);
texec=difftime(tend,tbegin);
cout << "\nTime (s) " << texec << " s\n";
int val=0;
cin >> val;
return 0;
}
Je viens de le tester sur mon Mac:
Il y a peut-être une possibilité d'augmenter les performances de Java mais je ne vois pas comment.