理解C++相对于其他编程语言的性能优势

huangapple go评论83阅读模式
英文:

Understanding the Performance Advantages of C++ over Other Languages

问题

C++通常表现出比Java和Python更好的执行速度,这一性能差异的原因涉及多个因素。以下是其中一些可能的原因:

  1. 编译与解释:C++代码在编译时被转换成本地机器码,而Java和Python是解释性语言,需要在运行时逐行解释执行。这使得C++在执行速度上有优势,因为它避免了解释器的开销。

  2. 内存管理:C++允许程序员手动管理内存,这可以导致更高效的内存使用。相比之下,Java和Python具有垃圾回收机制,这可能会在某些情况下引入一些性能开销。

  3. 静态类型检查:C++是一种静态类型语言,而Java也具备强类型检查,这使得编译器可以在编译时进行更多的优化和错误检查。Python是一种动态类型语言,这意味着类型检查通常需要在运行时进行,这可能导致性能损失。

  4. 编译器优化:C++编译器可以执行更多的优化,例如内联函数、循环展开和指令级优化,以提高代码的执行速度。Java和Python也有编译器优化,但通常不够强大。

  5. 并行处理:C++较容易实现多线程和并行计算,这可以更好地利用多核处理器。Java和Python也支持多线程,但在某些情况下,线程管理开销可能会降低性能。

  6. 数据结构和库:C++具有丰富的标准库和第三方库,这些库通常经过高度优化,可以提高性能。Java和Python也有大量库可用,但它们可能不如C++的库进行了同样程度的优化。

在提高Java和Python性能的情况下,您可以考虑以下一些方法:

  • Java中可以尝试使用Just-In-Time (JIT)编译器的优化选项,例如使用 -XX:+AggressiveOpts-XX:+UseParallelGC 等选项来改善性能。

  • Python中,您可以考虑使用JIT编译器,例如PyPy,以提高某些计算密集型任务的性能。

  • 在Java和Python中,您可以优化算法和数据结构的选择,以减少不必要的计算和内存开销。

  • 并行化:使用多线程或多进程来并行处理任务,以充分利用多核处理器。

总之,C++通常在执行速度上具有优势,但Java和Python在其他方面,如开发速度和可维护性方面可能更具优势。选择编程语言时,需要根据具体的需求和优先级考虑这些因素。

英文:

Why does C++ generally exhibit better execution speed than Java and Python? What factors contribute to this performance disparity? I conducted a series of tests to compare the execution speeds of these languages and seek a deeper understanding of the underlying reasons.

Context: As a computer science student, I have been exploring various programming languages to comprehend their performance characteristics. Through my experiments, I have consistently observed that C++ tends to outperform Java and Python in terms of execution speed. However, I desire a comprehensive understanding of the factors contributing to this performance difference.

Hardware and Compilation Details: To ensure a fair comparison, I executed the same algorithm using identical logic and datasets across all three languages. The experiments were conducted on a system equipped with an Intel Core i7 processor (8 cores) and 16 GB of RAM.

For the C++ code, I utilized GCC 10.2.0 with the following compilation flags:

g++ -O3 -march=native -mtune=native -std=c++17 -o program program.cpp

Java was executed using OpenJDK 11.0.1 with the following command:

java -Xmx8G -Xms8G Program

Python code was executed using Python 3.9.0 as follows:

python3 program.py
C++ code:

#include <iostream>
#include <chrono>
#include <vector>
#include <random>

// Function to generate a random matrix of size m x n
std::vector<std::vector<int>> generateRandomMatrix(int m, int n) {
    std::vector<std::vector<int>> matrix(m, std::vector<int>(n));
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(1, 100);

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            matrix[i][j] = dis(gen);
        }
    }

    return matrix;
}

// Matrix multiplication function
std::vector<std::vector<int>> matrixMultiplication(const std::vector<std::vector<int>>& A, const std::vector<std::vector<int>>& B) {
    int m = A.size();
    int n = B[0].size();
    int k = B.size();

    std::vector<std::vector<int>> result(m, std::vector<int>(n, 0));

    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int x = 0; x < k; ++x) {
                result[i][j] += A[i][x] * B[x][j];
            }
        }
    }

    return result;
}

int main() {
    // Generate random matrices A and B of size 3 x 3
    std::vector<std::vector<int>> A = generateRandomMatrix(3, 3);
    std::vector<std::vector<int>> B = generateRandomMatrix(3, 3);

    // Measure execution time
    auto start = std::chrono::steady_clock::now();

    // Perform matrix multiplication
    std::vector<std::vector<int>> result = matrixMultiplication(A, B);

    auto end = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

    std::cout << "Execution time (C++): " << duration << " microseconds" << std::endl;

    return 0;
}

Java code:

import java.util.Arrays;
import java.util.Random;

public class Program {
    // Function to generate a random matrix of size m x n
    public static int[][] generateRandomMatrix(int m, int n) {
        int[][] matrix = new int[m][n];
        Random random = new Random();

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                matrix[i][j] = random.nextInt(100) + 1;
            }
        }

        return matrix;
    }

    // Matrix multiplication function
    public static int[][] matrixMultiplication(int[][] A, int[][] B) {
        int m = A.length;
        int n = B[0].length;
        int k = B.length;

        int[][] result = new int[m][n];

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                for (int x = 0; x < k; ++x) {
                    result[i][j] += A[i][x] * B[x][j];
                }
            }
        }

        return result;
    }
    public static void main(String[] args) {
        // Generate random matrices A and B of size 3 x 3
        int[][] A = generateRandomMatrix(3, 3);
        int[][] B = generateRandomMatrix(3, 3);

        // Measure execution time
        long start = System.nanoTime();

        // Perform matrix multiplication
        int[][] result = matrixMultiplication(A, B);

        long end = System.nanoTime();
        long duration = end - start;

        System.out.println("Execution time (Java): " + duration + " nanoseconds");
    }
}

Python code:

import time
import numpy as np
import random

# Function to generate a random matrix of size m x n
def generateRandomMatrix(m, n):
    return [[random.randint(1, 100) for _ in range(n)] for _ in range(m)]

# Matrix multiplication function
def matrixMultiplication(A, B):
    A = np.array(A)
    B = np.array(B)
    result = np.dot(A, B)
    return result.tolist()

if __name__ == "__main__":
    # Generate random matrices A and B of size 3 x 3
    A = generateRandomMatrix(3, 3)
    B = generateRandomMatrix(3, 3)

    # Measure execution time
    start = time.time()

    # Perform matrix multiplication
    result = matrixMultiplication(A, B)

    end = time.time()
    duration = (end - start) * 1e6

    print("Execution time (Python): {} microseconds".format(duration))

I noticed a substantial performance difference in favor of C++. The execution times demonstrate C++'s superiority over Java and Python.

I understand that C++ is typically compiled, Java employs virtual machine emulation, and Python is interpreted. Consequently, I acknowledge that differences in execution approaches and compiler optimizations may significantly contribute to these performance disparities. Nonetheless, I would appreciate a more detailed explanation of the specific reasons underlying the observed performance differences.

Furthermore, I have taken the recommendation into account and conducted longer tests, running the algorithms on larger datasets to minimize the impact of initial startup costs for Java and Python. Nevertheless, the performance gap between C++ and the other languages remains substantial.

Could someone shed light on why C++ consistently outperforms Java and Python in this scenario? Are there specific compiler flags or runtime settings that could be adjusted in Java and Python to enhance their performance in similar computational tasks?

Thank you for sharing your insights and expertise!

答案1

得分: 3

让我开始(也许会完成,不确定我会有多有雄心)一个离题的讨论。

基准测试很困难

根据你所获得的时间,我认为你正在计时整个程序的执行。不幸的是,这几乎肯定是由写入标准输出的时间所主导的,所以如果(例如)你将输出重定向到文件,它很可能会运行得快得多。根据测试环境等的不同,这很容易产生相当误导性的结果。

举个例子,我猜想你正在Windows下计时,因为它的控制台输出速度相对较慢。当我在Linux下运行你的Python代码时,我得到了0.108秒的时间,比你的C++代码快得多。但这几乎肯定主要是因为Linux的控制台输出比Windows快得多,与Python或C++本身没有什么关系。

让我们只计时C++部分的计算并看看结果如何。

// 你编写的先前的代码

int main() {
    int n = 1000;
    using namespace std::chrono; // 添加的

    auto start = high_resolution_clock::now(); // 添加的

    std::vector<int> primeNumbers = generatePrimes(n);

    auto stop = high_resolution_clock::now(); // 添加的

    std::cout << "前 " << n << " 个质数:";
    for (int prime : primeNumbers) {
        std::cout << prime << " ";
    }    
    std::cout << std::endl;

    // 添加的:
    std::cout << "时间:" << duration_cast<microseconds>(stop-start).count() << "us\n";
    return 0;
}

在我的机器上(并不特别快),我得到了:

时间:394us

所以即使程序运行时间较长,实际上几乎所有的时间都用在显示输出上。实际的计算只花了0.394毫秒,占总时间的一小部分。

尽管我做了一些补充,但计时很可能仍然不完美,说得婉转些。特别是,现代CPU可以乱序执行指令,所以计算可能在我获取开始时间之前开始执行,或者在我获取停止时间之后完成。这也可以防止,但这更加困难(在这种情况下,我猜测时钟产生的时间至少相当准确,因为你通常必须至少有一个合理的猜测你期望的时间,如果你的结果偏离了一个显著的因素,你可能需要更仔细地研究事情)。

C++ vs. Python

C++和Python之间的主要区别在于动态类型和静态类型。在Python中,变量可以附加到任何类型的值,并且该类型可以在其生命周期内更改。例如,我们可以合法地有一个变量a,一次保存数字,另一次保存字符串。

a = 3
print(a, type(a))
a = "three"
print(a, type(a))

结果:

3 <class 'int'>
three <class 'str'>

因此,当你在Python中执行几乎任何操作时,代码必须在运行时查找每个变量的类型,以确定正在执行的实际操作,因为它所执行的操作取决于它们的类型。

在C++中,类型大多是静态的(尽管你可以使用std::any<>std::variant<>来获得至少一种有限的动态类型模拟)。当然,你的代码中的类型完全是静态的。这意味着编译器在编译时已经查找了要在操作数上应用的操作,所以它根本不会影响执行时间。

在这种情况下,你正在执行大量简单的操作。在Python代码中,很可能大部分时间实际上都花在查找操作数类型和确定这些操作数适合哪些操作上,只有很小一部分时间用于执行操作本身。

现在,确实可以通过足够聪明的Python实现来预先推断类型。当我执行类似a = 7的操作时,a显然是一个int。根据之后应用于a的操作,它通常可以跟踪a在其生命周期内将具有的类型,并为该类型生成特定的代码。

因此,速度惩罚的大部分不一定是Python作为一种语言固有的,而是与它的最常见的实现(尤其是对于像你写的这种不依赖于用户输入的代码)有关。至少从理论上讲,可以编写一个实现,它可能会提供与C++相似得多的性能(特别是对于像你写的这种不依赖于用户输入的代码)。

但是,我猜测,大多数Python爱好者对于这样做的反应通常会是:“如果你想要C++,你知道在哪里可以找到它。”至少对于他们中的绝大多数人来说,有更重要的方式来花费那些时间和精力(而且这将需要大量的时间和精力)。

英文:

Let me start (and maybe finish--not sure how ambitious I'll be) with a digression.

Benchmarking is Hard

Based on the times you're getting, it appears likely to me that you're timing execution of the entire program. Unfortunately, this is almost certainly dominated by the time taken to write the data to standard output, so if (for example) you redirected output to a file, chances are pretty good it would run a lot faster. Depending on testing environment and such, it would be fairly easy for this to produce fairly misleading results.

Just for example, I'd guess you're timing this under Windows, which has fairly slow console output. When I run your Python code under Linux, I get a time of 0.108 seconds--Python running faster than you got for C++. But that's almost certainly due primarily to Linux having much faster console output than Windows, not anything related to Python or C++ themselves at all.

Let's time just the computation part of the C++ and see what we get.

// preceding code as you wrote it

int main() {
    int n = 1000;
    using namespace std::chrono; // added

    auto start = high_resolution_clock::now(); // added

    std::vector&lt;int&gt; primeNumbers = generatePrimes(n);

    auto stop = high_resolution_clock::now(); //added

    std::cout &lt;&lt; &quot;First &quot; &lt;&lt; n &lt;&lt; &quot; prime numbers: &quot;;
    for (int prime : primeNumbers) {
        std::cout &lt;&lt; prime &lt;&lt; &quot; &quot;;
    }    
    std::cout &lt;&lt; std::endl;

    // added:
    std::cout &lt;&lt; &quot;Time: &quot; &lt;&lt; duration_cast&lt;microseconds&gt;(stop-start).count() &lt;&lt; &quot;us\n&quot;;
    return 0;
}

On my machine (which isn't particularly fast) I get:

Time: 394us

So even though the program took much longer to run, essentially all of that was consumed with displaying the output. The actual computation took only 0.394 milliseconds--a tiny fraction of the overall time.

It is still important to write out that result though--if you don't write out the result, the compiler can often detect that you're not using, it, and simply not compute it at all.

Even with the additions I made, the timing may easily be less than perfect, to put to mildly. In particular, modern CPUs can execute instructions out of order, so it's possible that the computation started before I grabbed the start time and/or finished after I grabbed the stop time. This can also be prevented, but it's more difficult (and in this case, doing a quick calculation of how much time it should take, I'd guess what the clock produced is at least reasonably accurate). But you often have to start with at least a decent guess at what you've expecting, and if your result is off by a significant factor, you may need to look at things more carefully.

C++ vs. Python

The big difference between C++ and Python hinges around dynamic vs. static typing. In Python, a variable can be attached to a value of any type, and that type can change over its lifetime. For example, we can perfectly legally have some variable a that holds a number at one time, and string at a different time.

a = 3
print(a, type(a))
a = &quot;three&quot;
print(a, type(a))

Result:

3 &lt;class &#39;int&#39;&gt;
three &lt;class &#39;str&#39;&gt;

Because of this, when you do almost any operation in Python, the code has to look up (at runtime) the type of each variable, to figure out what actual operation is being done, because the operation it carries out depends on their types.

In C++ types are mostly static (though you can use std::any&lt;&gt; and std::variant&lt;&gt; to get at least a limited imitation of dynamic types). Certainly the types in your code are entirely static. That means the compiler has done that lookup of what operation to apply to the operands at compile time, so it doesn't affect execution time at all.

In this case, you're doing a large number of simple operations. In the Python code, it's quite likely that most of the time is really spent looking up operand types, and figuring out what actions are appropriate for those operands, and only a small fraction of the time to actually doing the operation itself.

Now, it is true that a sufficiently smart implementation of Python could often (probably even usually) infer types ahead of time. When I execute something like a = 7, a is obviously an int. Based on the operations applied to a afterwards, it could often track what type a is going to have throughout its lifetime, and generate code specifically for that type.

So, much of the speed penalty isn't necessarily inherent to Python as a language, only to the most common implementation(s) of it. At least theoretically, an implementation could be written that would probably provide performance much closer to C++ (especially for code like you've written where nothing depends on user inputs).

My guess, however, is that the average Pythonista's reaction to doing this would generally be: "if you want C++, you know where to find it." At least for the vast majority of them, there are more important ways to spend that time and effort (and it would be a lot of time and effort).

huangapple
  • 本文由 发表于 2023年6月29日 04:05:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76576390.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定