In C++, am I paying for what I am not eating?

后端 未结 13 1930
既然无缘
既然无缘 2020-12-12 12:39

Let\'s consider the following hello world examples in C and C++:

main.c

#include 

int main()
{
    printf(\"Hello world\\n\");
    r         


        
相关标签:
13条回答
  • 2020-12-12 12:40

    While the existing technical answers are correct, I think that the question ultimately stems from this misconception:

    It is famous that in C++ you pay for what you eat.

    This is just marketing talk from the C++ community. (To be fair, there's marketing talk in every language community.) It doesn't mean anything concrete that you can seriously depend on.

    "You pay for what you use" is supposed to mean that a C++ feature only has overhead if you're using that feature. But the definition of "a feature" is not infinitely granular. Often you will end up activating features that have multiple aspects, and even though you only need a subset of those aspects, it's often not practical or possible for the implementation to bring the feature in partially.

    In general, many (though arguably not all) languages strive to be efficient, with varying degrees of success. C++ is somewhere on the scale, but there is nothing special or magical about its design that would allow it to be perfectly successful in this goal.

    0 讨论(0)
  • 2020-12-12 12:41

    The Input / Output functions in C++ are elegantly written and are designed so they are simple to use. In many respects they are a showcase for the object-orientated features in C++.

    But you do indeed give up a bit of performance in return, but that's negligible compared to the time taken by your operating system to handle the functions at a lower level.

    You can always fall back to the C style functions as they are part of the C++ standard, or perhaps give up portability altogether and use direct calls to your operating system.

    0 讨论(0)
  • 2020-12-12 12:42

    Your listings are indeed comparing apples and oranges, but not for the reason implied in most other answers.

    Let’s check what your code actually does:

    C:

    • print a single string, "Hello world\n"

    C++:

    • stream the string "Hello world" into std::cout
    • stream the std::endl manipulator into std::cout

    Apparently your C++ code is doing twice as much work. For a fair comparison we should combine this:

    #include <iostream>
    
    int main()
    {
        std::cout<<"Hello world\n";
        return 0;
    }
    

    … and suddenly your assembly code for main looks very similar to C’s:

    main:
            sub     rsp, 8
            mov     esi, OFFSET FLAT:.LC0
            mov     edi, OFFSET FLAT:_ZSt4cout
            call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
            xor     eax, eax
            add     rsp, 8
            ret
    

    In fact, we can compare the C and C++ code line by line, and there are very few differences:

    sub     rsp, 8                      sub     rsp, 8
    mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                    >   mov     edi, OFFSET FLAT:_ZSt4cout
    call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
    xor     eax, eax                    xor     eax, eax
    add     rsp, 8                      add     rsp, 8
    ret                                 ret
    

    The only real difference is that in C++ we call operator << with two arguments (std::cout and the string). We could remove even that slight difference by using a closer C eqivalent: fprintf, which also has a first argument specifying the stream.

    This leaves the assembly code for _GLOBAL__sub_I_main, which is generated for C++ but not C. This is the only true overhead that’s visible in this assembly listing (there’s more, invisible overhead for both languages, of course). This code performs a one-time setup of some C++ standard library functions at the start of the C++ program.

    But, as explained in other answers, the relevant difference between these two programs won’t be found in the assembly output of the main function since all the heavy lifting happens behind the scenes.

    0 讨论(0)
  • 2020-12-12 12:42

    You are paying for a mistake. In the 80s, when compilers aren't good enough to check format strings, operator overloading was seen as a good way to enforce some semblance of type safety during io. However, every one of its banner features are either implemented badly or conceptually bankrupt from the start:

    <iomanip>

    The most repugnant part of the C++ stream io api is the existence of this formatting header library. Besides being stateful and ugly and error prone, it couples formatting to the stream.

    Suppose you want to print out an line with 8 digit zero filled hex unsigned int followed by a space followed by a double with 3 decimal places. With <cstdio>, you get to read a concise format string. With <ostream>, you have to save the old state, set alignment to right, set fill character, set fill width, set base to hex, output the integer, restore saved state (otherwise your integer formatting will pollute your float formatting), output the space, set notation to fixed, set precision, output the double and the newline, then restore the old formatting.

    // <cstdio>
    std::printf( "%08x %.3lf\n", ival, fval );
    
    // <ostream> & <iomanip>
    std::ios old_fmt {nullptr};
    old_fmt.copyfmt (std::cout);
    std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
    std::cout.copyfmt (old_fmt);
    std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
    std::cout.copyfmt (old_fmt);
    

    Operator Overloading

    <iostream> is the poster child of how not to use operator overloading:

    std::cout << 2 << 3 && 0 << 5;
    

    Performance

    std::cout is several times slower printf(). The rampant featuritis and virtual dispatch does take its toll.

    Thread Safety

    Both <cstdio> and <iostream> are thread safe in that every function call is atomic. But, printf() gets a lot more done per call. If you run the following program with the <cstdio> option, you will see only a row of f. If you use <iostream> on a multicore machine, you will likely see something else.

    // g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp
    
    #define USE_STREAM 1
    #define REPS 50
    #define THREADS 10
    
    #include <thread>
    #include <vector>
    
    #if USE_STREAM
        #include <iostream>
    #else
        #include <cstdio>
    #endif
    
    void task()
    {
        for ( int i = 0; i < REPS; ++i )
    #if USE_STREAM
            std::cout << std::hex << 15 << std::dec;
    #else
            std::printf ( "%x", 15);
    #endif
    
    }
    
    int main()
    {
        auto threads = std::vector<std::thread> {};
        for ( int i = 0; i < THREADS; ++i )
            threads.emplace_back(task);
    
        for ( auto & t : threads )
            t.join();
    
    #if USE_STREAM
            std::cout << "\n<iostream>\n";
    #else
            std::printf ( "\n<cstdio>\n" );
    #endif
    }
    

    The retort to this example is that most people exercise discipline to never write to a single file descriptor from multiple threads anyway. Well, in that case, you'll have to observe that <iostream> will helpfully grab a lock on every << and every >>. Whereas in <cstdio>, you won't be locking as often, and you even have the option of not locking.

    <iostream> expends more locks to achieve a less consistent result.

    0 讨论(0)
  • 2020-12-12 12:45

    It is famous that in C++ you pay for what you eat. So, in this case, what am I paying for?

    That's simple. You pay for std::cout. "You pay for only what you eat" doesn't mean "you always get best prices". Sure, printf is cheaper. One can argue that std::cout is safer and more versatile, thus its greater cost is justified (it costs more, but provides more value), but that misses the point. You don't use printf, you use std::cout, so you pay for using std::cout. You don't pay for using printf.

    A good example is virtual functions. Virtual functions have some runtime cost and space requirements - but only if you actually use them. If you don't use virtual functions, you don't pay anything.

    A few remarks

    1. Even if C++ code evaluates to more assembly instructions, it's still a handful of instructions, and any performance overhead is still likely dwarfed by actual I/O operations.

    2. Actually, sometimes it's even better than "in C++ you pay for what you eat". For example, compiler can deduce that virtual function call is not needed in some circumstances, and transform that into non-virtual call. That means you may get virtual functions for free. Isn't that great?

    0 讨论(0)
  • 2020-12-12 12:45

    In addition to what all the other answers have said,
    there's also the fact that std::endl is not the same as '\n'.

    This is an unfortunately common misconception. std::endl does not mean "new line",
    it means "print new line and then flush the stream". Flushing is not cheap!

    Completely ignoring the differences between printf and std::cout for a moment, to be functionally eqvuialent to your C example, your C++ example ought to look like this:

    #include <iostream>
    
    int main()
    {
        std::cout << "Hello world\n";
        return 0;
    }
    

    And here's an example of what your examples should be like if you include flushing.

    C

    #include <stdio.h>
    
    int main()
    {
        printf("Hello world\n");
        fflush(stdout);
        return 0;
    }
    

    C++

    #include <iostream>
    
    int main()
    {
        std::cout << "Hello world\n";
        std::cout << std::flush;
        return 0;
    }
    

    When comparing code, you should always be careful that you're comparing like for like and that you understand the implications of what your code is doing. Sometimes even the simplest examples are more complicated than some people realise.

    0 讨论(0)
提交回复
热议问题