为什么我的Java Merge Sort比C++实现更快

Why is my Java Merge-Sort faster than my C++ implementation?

本文关键字:C++ 实现 Sort 我的 Java Merge 为什么      更新时间:2023-10-16

我在Java和C++中实现了Merge Sort,并试图尽可能类似地实现它们。这两种算法都有效,我对它们进行了多次测试。问题是我的Java实现比C++实现快得多,我想知道为什么。我不敢相信Java会更快,所以我想我在其中一个实现中犯了一个错误。为了测量运行时,我创建了一个类"Person",它有两个字符串属性(forename,lastname)。在C++中我使用std::vector<Person*>,在Java中我使用了ArrayList<Person>。此外,我在C++中重载了operator<来比较两个Person(比较姓氏,如果等于则比较名字)。在Java中,我实现了Comparable<Person>接口来比较两个人。

你能在我的代码中找到错误吗?或者Java会更快或C++会更慢的原因吗?如有任何帮助,我们将不胜感激

我的Java代码:

public void mergeSort(List<T> list) {
if (list.size() <= 1) {
return;
}
int subLength = (int) (list.size() / 2);
List<T> first = new ArrayList<T>(list.subList(0, subLength));
List<T> second = new ArrayList<T>(list.subList(subLength, list.size()));

mergeSort(first);
mergeSort(second);
merge(first, second, list);
return;
}
private void merge(List<T> first, List<T> second, List<T> result) {
int firstPos = 0, secondPos = 0, resultPos = 0;
while (firstPos < first.size() && secondPos < second.size()) {
if (first.get(firstPos).compareTo(second.get(secondPos)) < 0) {
result.set(resultPos, first.get(firstPos));
firstPos++;
} else {
result.set(resultPos, second.get(secondPos));
secondPos++;
}
resultPos++;
}
for (int i = firstPos; i < first.size(); i++) {
result.set(resultPos, first.get(i));
resultPos++;
}
for (int i = secondPos; i < second.size(); i++) {
result.set(resultPos, second.get(i));
resultPos++; 
}
}

我的C++代码:

注意:我使用了两种模板方法使合并可以与PersonPerson*一起使用。

template<typename T>
T * ptr(T & obj) { return &obj; } 
template<typename T>
T * ptr(T * obj) { return obj; }

void mergeSort(std::vector<T> &list) {
if (list.size() <= 1) {
return;
}
int subLength = (int)(list.size() / 2);
std::vector<T> first(list.begin(), list.begin() + subLength);
std::vector<T> second(list.begin() + subLength, list.end());
mergeSort(first);
mergeSort(second);
merge(first, second, list);
}
void merge(const std::vector<T> &first, const std::vector<T> &second, std::vector<T> &result) {
int firstPos = 0, secondPos = 0, resultPos = 0;
while (firstPos < first.size() && secondPos < second.size()) {
if (*ptr(first[firstPos]) < *ptr(second[secondPos])) {
result[resultPos] = first[firstPos];
firstPos++;
}
else {
result[resultPos] = second[secondPos];
secondPos++;
}
resultPos++;
}
for (int i = firstPos; i < first.size(); i++) {
result[resultPos] = first[i];
resultPos++;
}
for (int i = secondPos; i < second.size(); i++) {
result[resultPos] = second[i];
resultPos++;
}
}

第1版和第2版:

我的设置配置:

我使用了100万、1000万和2000万人来测试实现。我真的不在乎用多少人来测试它,Java总是更快。

我用以下方式测试它:创建人员并初始化我的MergeSort-类。然后我开始测量,并调用我的mergeSort-方法。排序完成后,我停止测量。(删除发生在时间测量之后)。对于Java中的时间测量,我使用System.nanoTime(),在C++中使用chrono::high_resolution_clock::time_point

当然,我在"发布"模式下编译了C++(优化:O2,首选更快的代码)。

我的测试PC:

  • 英特尔i5 2500
  • 8GB DDR3内存
  • Windows 10教育

第3版:

有一件事我忘了提。为了使用简单的数据类型和对象,我以通用的方式实现了该算法。当我在Java中使用std::vector<int>ArrayList<Integer>时,我的C++实现要快得多。我的第一次尝试是使用std::vector<Person>,但速度更慢。所以我的猜测是,它进行了深度复制,而不是浅层复制,这就是为什么我切换到Person*,因为我认为当复制发生时,只有指针会被复制。

TL;DR-Java版本执行的阵列复制要少得多。

ArrayList的ArrayList构造函数(参见第167行)在传递Collection时使用Collection.toArray(),如果需要,还可以使用Arrays.copyOfArrayList的情况下,不需要进行复制-toArray()返回对底层数组的引用

还要注意,if (elementData.getClass() != Object[].class)将导致阵列不再被复制。

ArrayList对象上的JavaList.subList不复制任何底层数组,它只是返回一个由原始数组支持的ArrayList,但仅限于所需的元素。

因此——在可能的情况下,整个机制使用的子列表实际上只是引用原始数组——不需要复制。

对C++不太熟悉,但我怀疑有很多数组复制和分配正在进行,而这不需要Java来完成。

添加-正如@ThomasKläger正确指出的那样,ArrayList.toArray实际上返回了数组的防御副本,所以我上面错了。

我在你的陈述中看到的一件事是:

删除发生在时间测量之后

你说的是删除Person对象,显然不是说容器,比如C++正在堆栈上创建和清理的firstsecond

std::vector<T> first(list.begin(), list.begin() + subLength);
std::vector<T> second(list.begin() + subLength, list.end());

而Java在堆上创建它们,直到它到达为止才清理它们(所以在你停止计时之后):

List<T> first = new ArrayList<T>(list.subList(0, subLength));
List<T> second = new ArrayList<T>(list.subList(subLength, list.size()));

因此,您正在对C++进行容器清理而Java不进行容器清理进行计时。


我必须在这里问,编写自己的合并排序有什么意义?最好的Java和C++代码将使用该语言已经提供的排序算法。如果你想计时,至少要计时优化的算法。

此外,我不会在时间比较上花太多精力。C++会更快,通常也会有更多的工作要写。如果速度对你来说足够重要,以至于要花时间,你可能想使用C++。如果开发时间是王道,那么您将希望使用Java。