为什么我的Java Merge Sort比C++实现更快
Why is my Java Merge-Sort faster than my C++ implementation?
我在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++代码:
注意:我使用了两种模板方法使合并可以与Person
和Person*
一起使用。
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.copyOf
在。ArrayList
的情况下,不需要进行复制-toArray()
返回对底层数组的引用
还要注意,if (elementData.getClass() != Object[].class)
将导致阵列不再被复制。
ArrayList
对象上的JavaList.subList
不复制任何底层数组,它只是返回一个由原始数组支持的ArrayList
,但仅限于所需的元素。
因此——在可能的情况下,整个机制使用的子列表实际上只是引用原始数组——不需要复制。
对C++不太熟悉,但我怀疑有很多数组复制和分配正在进行,而这不需要Java来完成。
添加-正如@ThomasKläger正确指出的那样,ArrayList.toArray
实际上返回了数组的防御副本,所以我上面错了。
我在你的陈述中看到的一件事是:
删除发生在时间测量之后
你说的是删除Person
对象,显然不是说容器,比如C++正在堆栈上创建和清理的first
和second
:
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。
- 如果没有malloc,链表实现将失败
- 如何在c++中实现处理器调度模拟器
- 如何在c++中使用引用实现类似python的行为
- 实现无开销push_back的最佳方法是什么
- 使用简单类型列表实现的指数编译时间.为什么
- 如何在BST的这个简单递归实现中消除警告
- 实现一个在集合上迭代的模板函数
- 我应该实现右值推送功能吗?我应该使用std::move吗
- 如何正确实现和访问运算符的各种自定义枚举器
- C++Union/Struct位域的实现和可移植性
- 这个极客对极客的trie实现是否存在内存泄漏问题
- 在c++中实现LinkedList时,应出现未处理的错误
- 为左值和右值的包装器实现C++范围
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 使用GSoap实现ONVIF
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 用于AVX的ln(x)的实现,m256
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在C++中,如何在类和函数(可能是模板化的)的头中编写完整的实现
- std::random_device是如何实现的