性能问题:Java与C++

rea*_*404 68 c++ java algorithm performance

我一直听说C++比Java更有效(这就是大多数游戏都是用C++开发的原因).

我写了一个小算法来解决Java和C++中的"八皇后谜题",使用完全相同的算法,然后开始提高数字或方块.当到达20*20甚至22*22的检查板时,看起来Java更有效(3秒对C++的66秒).

我不知道为什么,但我从C++开始,所以我可能会犯一些巨大的性能错误,所以我很乐意接受任何可以帮助我理解正在发生的事情的信息.

下面是我在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是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;
}
Run Code Online (Sandbox Code Playgroud)

Bri*_*ian 91

std::list在C++中是一个链表,而是java.util.ArrayList一个数组.尝试更换std::liststd::vector.此外,请务必在启用优化的情况下进行编译.

  • @Jakub好点.使用`int`与带有`double`的C++代码的Java代码不是公平的比较. (21认同)
  • 在Point结构中用int替换double可以让编译器生成更好的代码. (10认同)
  • @ realUser404对我来说,在这种情况下通过引用传递较慢(我猜因为`Point`对象不是那么大).另外,你使用什么编译器?对我来说,在使用优化(`g ++ - 4.9 -O2`)编译时,原始C++代码已经比原始Java代码更快. (4认同)
  • @Jakub我的想法完全正确.错误的数据结构加上错误的数据类型.此外,为了帮助我们的朋友彼得,我认为每种语言都有自己的STL怪癖.流利,需要了解图书馆和习语. (4认同)
  • 你可能想要详细说明`std :: list`结构对于这个程序的效率低于`std :: vector`.是一个问题,(双向链接)列表上的基本操作(例如迭代器的增量)需要比向量更多的周期,或者是否使用了对列表来说基本上较慢的事情(如索引的元素访问) ?换句话说,这两个实现是否具有相同的渐近复杂度,它们之间只是一个常数因子,或者不是? (3认同)

Mar*_*ork 89

更新:

对C++的更改

  • 正如所写:
    编译失败
  • 替换math.h => cmath
    27610毫秒
  • 添加-O3标志
    4416毫秒
  • 用std :: vector替换std :: list
    2294毫秒
  • 用std :: pair替换Point
    2384毫秒
  • 使verifierNonPrise const正确
    2351毫秒
  • 在verifierNonPrise中用std::find_if
    929毫秒替换了循环
  • 用int替换double(使其等同于Java)
    549毫秒

对Java的更改

  • 写为
    3459毫秒
  • verifierNonPrise提前更改返回
    368毫秒

Java Vs C++

> 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
Run Code Online (Sandbox Code Playgroud)

最终守则:

#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;    
}
Run Code Online (Sandbox Code Playgroud)

  • @Deduplicator:两者之间的速度差异(大多数)是一个神话.有关此问题的大量文章.当您看到差异时,是因为您忘记拨打[sync_with_stdio](http://www.cplusplus.com/reference/ios/ios_base/sync_with_stdio/) (2认同)

Net*_*peC 30

测试此版本,使用C++ 11功能进行更新.用GCC 4.9.0测试-std=c++11.在Celeron 1.6 GHz,512 MB RAM上测试.

我的电脑中的时间:
原始:持续时间(毫秒):12658
第一版:持续时间(毫秒):3616
优化版本:持续时间(毫秒):1745

变化是:

  • 使用vector而不是list Benchmark来自Stroustrup的单词.
  • 尽可能使用const,如果知道值不变,编译器能够进行更多优化.
  • 使用std :: pair而不是Point.
  • 使用带有常量迭代器的新for循环.

资源:

#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;
}
Run Code Online (Sandbox Code Playgroud)

一些更深刻的变化.

变化包括:

  • 尽早回来.女王一旦不能放置.
  • 回到更简单的Point类.
  • 使用find_if算法搜索女王位置.

来源(一些建议更新):

#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;
}
Run Code Online (Sandbox Code Playgroud)

  • @NetVipeC这里没有编译器可以为`std :: vector`之外的任何东西做任何分配.使用课程会更清晰,也更慢.或者因为你正在使用C++ 11,一个带有列表初始化的结构.当然,`std :: pair`在这里是混淆,因为成员是_not_ first和second,但是`x`和`y`(具有明确的语义含义); `std :: pair`对于快速实验很方便,但在严格的代码中使用它是一种反模式. (3认同)
  • 不知道为什么`std :: pair`?从设计的角度来看,它绝对是您想要避免的.我也怀疑使用`emplace_back`会产生重大影响; 它更精细.(使用`std :: vector`并通过引用传递无疑是最大的胜利.) (2认同)

Dur*_*dal 19

将托管的,动态编译的语言(如Java)与静态编译的语言(如C++)进行比较非常困难.

在某种意义上,你总是将苹果与橙子进行比较,因为它们在概念上非常不同.它首先使用标准库(ArrayList vs std :: list/vector),它们可能具有可能截然不同的性能特征,即使您的代码在两种语言中看起来都相似.

然后在Java中存在微基准测试的固有问题(Java中的短测试总是较慢,因为JIT将在决定编译的内容和方式之前观察程序流程).对于C++的编译器选项也是如此,即使源代码的结构(独立编译和链接的类与单个文件源)也会产生显着差异(因为它会改变C++编译器对其他类的"洞察力") .

接下来是内存管理,垃圾收集与手动内存管理(智能指针等仍然被认为是手动内存管理)的一般差异.

更不用说像C++中显式声明方法虚拟所需的一般语言差异,而在Java中,默认情况下每个成员方法都是虚拟的(如果它在运行时真的是虚拟的,那么就可以计算出来).

由于存在所有这些差异,总会出现一种情况,即一种情况会比另一种措施具有巨大的优势.一个范围非常有限的简单测试(就像你在这里的测试一样)对每种语言的整体说法很少.

人们经常倾向于忽视的另一点是:你对语言的效率如何 - 速度并不是一切(看看在一些领域中成功的脚本语言,尽管在仅仅考虑执行速度时几乎没有竞争力).缺乏性能可能会造成严重损失,但生产率也会降低.

  • @juanchopanza那么任务的标题应该是"如何改进这个C++代码",并且最好简单地放在codereview上.虽然其他答案很好地涵盖了可能的优化,但它们都忽略了本质上无法比拟的方面 - 并且一些优化建议使得结果更加无法比拟.正确的做法是将C++版本更改为vector,因为它更类似于ArrayList.但是,其他方面只是增加了两个实现之间的差异,即*将苹果与橙子进行比较. (7认同)
  • 我认为问题的关键在于OP会(正确地)期望C++在这种特殊情况下更快,并且惊讶地发现它的速度要慢一些.这里没有苹果或橘子. (2认同)