Is there a performance degradation / penalty in using pure C library in C++ code?
Asked Answered
S

3

16

I saw this link but I'm not asking for a performance degradation for code using "extern". I mean without "extern", is there "context switching" when using C library in C++? Are there any problems when using pure C (not class-wrapped) functions in C++ application?

Spinal answered 30/12, 2017 at 2:26 Comment(7)
No, there won't be any performance penalty.Flossie
C++ is built upon C, so naturally, it is backwards compatible.Crosstree
What you mean "without extern"? How are you calling your C functions from C++ if they are not somehow declared extern "C"?Emersion
There is no pure C without extern "C" in C++. A function without a "class wrapper" is C++.Beebread
@GaryHayes Sorry, but that's BS. C++ is not "built upon C". Not all C++ is valid C and not all C is valid C++. C++ is not C, so speaking of backward compatibility as if C++ was C2.0 is simply incorrect. There is certain compatibility between the two languages, but not backwards compatibility.Hemoglobin
@Flossie No, there often be at least some penalties in either C or C++. For example printf may be faster than cout due to the lack of complex object hierachy calls, but it may be slower than cout because the C++ compiler already knows the type of the variable and will call the correct overload function directly instead of leaving for the function to parse at runtime https://mcmap.net/q/73746/-39-printf-39-vs-39-cout-39-in-c/995714 https://mcmap.net/q/271505/-which-is-faster-and-which-is-more-flexible-printf-or-cout-duplicate/995714 C also doesn't have templates so qsort might very well be slower than std::sort as it knows nothing about the function being calledIntercalate
@Lưu Vĩnh Phúc: That's somewhat missing the point. We're talking about the price of a function call, not the efficiency of the function.Flossie
L
35

Both C and C++ are programming language specifications (written in English, see e.g. n1570 for the specification of C11) and do not speak about performance (but about behavior of the program, i.e. about semantics).

However, you are likely to use a compiler such as GCC or Clang which don't bring any performance penalty, because it builds the same kind of intermediate internal representation (e.g. GIMPLE for GCC, and LLVM for Clang) for both C and C++ languages, and because C and C++ code use compatible ABIs and calling conventions.

In practice extern "C" don't change any calling convention but disables name mangling. However, its exact influence on the compiler is specific to that compiler. It might (or not) disable inlining (but consider -flto for link-time-optimization in GCC).

Some C compilers (e.g. tinycc) produce code with poor performance. Even GCC or Clang, when used with -O0 or without explicitly enabling optimization (e.g. by passing -O1 or -O2 etc...) might produce slow code (and optimizations are by default disabled with it).

BTW, C++ was designed to be interoperable with C (and that strong constraint explains most of the deficiencies of C++).

In some cases, genuine C++ code might be slightly faster than corresponding genuine C code. For example, to sort an array of numbers, you'll use std::array and std::sort in genuine C++, and the compare operations in the sort are likely to get inlined. With C code, you'll just use qsort and each compare goes through an indirect function call (because the compiler is not inlining qsort, even if in theory it could...).

In some other cases, genuine C++ code might be slightly slower; for example, several (but not all) implementations of ::operator new are simply calling malloc (then checking against failure) but are not inlined.

In practice, there is no penalty in calling C code from C++ code, or C++ code from C code, since the calling conventions are compatible.

The C longjmp facility is probably faster than throwing C++ exceptions, but they don't have the same semantics (see stack unwinding) and longjmp doesn't mix well accross C++ code.

If you care that much about performance, write (in genuine C and in genuine C++) twice your code and benchmark. You are likely to observe a small change (a few percents at most) between C and C++, so I would not bother at all (and your performance concerns are practically unjustified).


Context switch is a concept related to operating system and multitasking and happens on processes running machine code executable during preemption. How that executable is obtained (from a C compiler, from a C++ compiler, from a Go compiler, from an SBCL compiler, or being an interpreter of some other language like Perl or bytecode Python) is totally irrelevant (since a context switch can happen at any machine instruction, during interrupts). Read some books like Operating Systems: Three Eeasy Pieces.

Locomotive answered 30/12, 2017 at 2:39 Comment(9)
I think OP assumed there might be a stack switching when jumping from C++ to C and vice versa. Sometimes this is required when transitioning from one language to another, althougth with C and C++ this is not the case. Go probably has to switch stack somehow when executing external C code due to the fact that it uses segmented stack for coroutines and thus requires specially generated code inserted into every function - which is not done for typical C library.Impute
AFAIK, Go does not switches stack in that case, but even if it did it is not a context switch, and would run very fast.Locomotive
@BasileStarynkevitch This is basically a user space context switch. It is much faster than OS context switch ofc. I don't know if Go does this, but it has to handle stack overflow in external C code somehow. So it either crashes or does stack switching, or maybe something inbetween..?Impute
He put "context switching" in quotes. I assumed he referring to some kind of thunking/proxy code between the C++ program and the C library.Flossie
Go handling of stacks described here: blog.cloudflare.com/how-stacks-are-handled-in-go. Critical parts of Go runtime like GC are executed on a separate stack. And they fall back to segmented stack for arbitrary C code. Looks like they cannot reliably execute C code. I expect C code to crash the process if things go wrong and 8kb stack gets overflown.Impute
But changing stack is very quick, just loading the stack pointer. Managing stacks is a different question. With GCC you could in theory compile your C code with -fsplit-stack to get the same behavior in C code and in Go code, but nobody does thatLocomotive
@BasileStarynkevitch Not sure how much information they need, but code executing on separate stack need some information to be able to return, so some additional work is required. Might be half of context switch.Impute
"for example, several (but not all) implementations of ::operator new are simply calling malloc but are not inlined" It may be much worse than that: On my machine, an ::operator new() implemented to call through to malloc() outperforms the standard ::operator new() by roughly a hundred CPU cycles per call...Phrasing
longjmp might be faster when the jump/exception actually happens, but the C++ exception throwing mechanism is almost certainly faster when no exception happens (i.e., it doesn't need a setjmp and is often free or nearly free).Emersion
E
13

At a basic level, no, you won't see any type of "switching" performance penalty when calling a C library from C++ code. For example calling from C++ a C method defined in another translation unit should have approximately identical performance to calling the same method implemented in C++ (in the same C-like way) in another translation unit.

This is because common implementations of C and C++ compilers ultimately compile the source down to native code, and calling an extern "C" function is efficiently supported using the same type of call that might occur for a C++ call. The calling conventions are usually based on the platform ABI and are similar in either case.

That basic fact aside, there might still be some performance downsides when calling a C function as opposed to implementing the same function in C++:

  • Functions implemented in C and declared extern "C" and called from C++ code usually won't be inlined (since by definition they aren't implemented in a header), which inhibits a whole host of possibly very powerful optimization0.
  • Most data types used in C++ code1 can't be directly used by C code, so for example if you have a std::string in your C++ code, you'll need to pick a different type to pass it to C code - char * is common but loses information about the explicit length, which may be slower than a C++ solution. Many types have no direct C equivalent, so you may be stuck with a costly conversion.
  • C code uses malloc and free for dynamic memory management, while C++ code generally uses new and delete (and usually prefers to hide those calls behind other classes as much as possible). If you need to allocate memory in one language that will be freed in other other, this may cause a mismatch where you need to call back into the "other" language to do the free, or may unnecessary copies, etc.
  • C code often makes heavy use of the C standard library routines, while C++ code usually uses methods from the C++ standard library. Since there is a lot of functional overlap, it is possible that a mix of C and C++ has a larger code footprint than pure C++ code since more C library methods are used2.

The concerns above would apply only when contrasting a pure C++ implementation versus a C one, and doesn't really mean there is a performance degradation when calling C: it is really answering the question "Why could writing an application in a mix of C and C++ be slower than pure C++?". Furthermore, the above issues are mostly a concern for very short calls where the above overheads may be significant. If you are calling a lengthy function in C, it is less of a problem. The "data type mismatch" might still bite you, but this can be designed around on the C++ side.


0 Interestingly, link-time optimization actually allows C methods to be inlined in C++ code, which is a little-mentioned benefit of LTO. Of course, this is generally dependent on building the C library yourself from source with the appropriate LTO options.

1 E.g., pretty much anything other than a standard layout type.

2 This is at least partially mitigated by the fact that many C++ standard library calls ultimately delegate to C library routines for the "heavy" lifting, such as how std::copy calls memcpy or memset when possible and how most new implementations ultimately call malloc.

Emersion answered 30/12, 2017 at 3:9 Comment(0)
R
4

C++ has grown and changed a lot since its inception, but by design it is backwards-compatible with C. C++ compilers are generally built from C compilers, but even more modernized with link-time optimizations. I would imagine lots of software can reliably mix C and C++ code, both in the user spaces and in the libraries used. I answered a question recently that involved passing a C++ class member function pointer, to a C-implemented library function. The poster said it worked for him. So it's possible C++ is more compatible with C than any programmers or users would think.

However, C++ works in many different paradigms that C does not, as it is object-oriented, and implements a whole spectrum of abstractions, new data types, and operators. Certain data types are easily translatable (char * C string to a std::string), while others are not. This section on GNU.org about C++ compiler options may be of some interest.

I would not be too worried or concerned about any decline in performance, when mixing the two languages. The end user, and even the programmer, would hardly notice any measurable changes in performance, unless they were dealing with big abstractions of data.

Rhombohedron answered 30/12, 2017 at 4:23 Comment(1)
Your link to GCC compiler options is terribly outdated (because GCC3.0 is obsolete since long time ago). Use gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.htmlLocomotive

© 2022 - 2024 — McMap. All rights reserved.