Есть ли снижение/штраф производительности при использовании чистой библиотеки C в коде C++?

Я видел эту ссылку, но я не прошу снижения производительности кода, использующего «extern». Я имею в виду без «extern», есть ли «переключение контекста» при использовании библиотеки C в C++? Есть ли какие-либо проблемы при использовании чистых функций C (не обернутых в классы) в приложении C++?


person John Doe    schedule 30.12.2017    source источник
comment
Нет, штрафа за производительность не будет.   -  person Steve    schedule 30.12.2017
comment
C++ построен на C, поэтому, естественно, он обратно совместим.   -  person Gary Hayes    schedule 30.12.2017
comment
Что вы имеете в виду без внешнего? Как вы вызываете свои функции C из C++, если они каким-то образом не объявлены extern "C"?   -  person BeeOnRope    schedule 30.12.2017
comment
В C++ нет чистого C без extern "C". Функция без оболочки класса является C++.   -  person    schedule 30.12.2017
comment
@GaryHayes Извините, но это чушь. C++ не основан на C. Не весь C++ является допустимым C, и не весь C является допустимым C++. C++ — это не C, поэтому говорить об обратной совместимости так, будто C++ — это C2.0, просто неверно. Существует определенная совместимость между двумя языками, но не обратная совместимость.   -  person Bakuriu    schedule 30.12.2017
comment
@Steve Нет, в C или C ++ часто бывают хотя бы некоторые штрафы. Например, printf может быть быстрее, чем cout из-за отсутствия сложных вызовов иерархии объектов, но может быть медленнее, чем cout, потому что компилятор C++ уже знает тип переменной и будет вызывать правильную функцию перегрузки напрямую, вместо того, чтобы переходить к функции. для анализа во время выполнения stackoverflow.com/q/2872543/995714 stackoverflow.com/q/3643828/995714 C также не имеет шаблонов, поэтому qsort вполне может быть медленнее, чем std::sort, поскольку он ничего не знает о вызываемой функции.   -  person phuclv    schedule 30.12.2017
comment
@Lưu Vĩnh Phúc: Это несколько упускает суть. Мы говорим о цене вызова функции, а не об эффективности функции.   -  person Steve    schedule 31.12.2017


Ответы (3)


И C, и C++ являются спецификациями языков программирования (написаны на английском языке, см., например, n1570 для спецификации C11) и говорят не о производительности (а о поведении программы, т.е. о семантика).

Однако вы, скорее всего, будете использовать такой компилятор, как GCC или Clang, которые не приводят к снижению производительности, поскольку создают одно и то же промежуточное внутреннее представление (например, GIMPLE для GCC и LLVM для Clang) как для C, так и для Clang. языки C++, а также поскольку в коде C и C++ используются совместимые ABI и соглашения о вызовах.

На практике extern "C" не меняет никакого соглашения о вызовах, но отключает изменение имен. Однако его точное влияние на компилятор специфично для этого компилятора. Он может (или нет) отключить встраивание (но рассмотрите -flto для оптимизации времени компоновки в GCC) .

Некоторые компиляторы C (например, tinycc) создают код с низкой производительностью. Даже GCC или Clang при использовании с -O0 или без явного включения оптимизации (например, путем передачи -O1 или -O2 и т. д...) может производить медленный код (и оптимизации по умолчанию с ним отключены).

Кстати, C++ был разработан для взаимодействия с C (и это сильное ограничение объясняет большинство недостатков C++).

В некоторых случаях подлинный код C++ может быть немного быстрее, чем соответствующий подлинный код C. Например, чтобы отсортировать массив чисел, вы будете использовать std::array и std::sort в подлинном C++, а операции сравнения в сортировке скорее всего, будут встроены. В коде C вы просто используете qsort, и каждое сравнение проходит косвенную вызов функции (поскольку компилятор не встраивает qsort, даже если теоретически это возможно...).

В некоторых других случаях подлинный код C++ может быть немного медленнее; например, некоторые (но не все) реализации ::operator new просто вызывают malloc (затем проверяют на наличие ошибки), но не встроены.

На практике вызов кода C из кода C++ или кода C++ из кода C не вызывает никаких проблем, поскольку соглашения о вызовах совместимы.

Средство C longjmp, вероятно, работает быстрее, чем создание исключений C++, но они не имеют одинаковую семантику (см. раскручивание стека), а longjmp плохо сочетается между собой код С++.

Если вы так сильно заботитесь о производительности, напишите (на подлинном C и на подлинном C++) вдвое больше кода и теста. Скорее всего, вы заметите небольшую разницу (максимум несколько процентов) между C и C++, поэтому я бы вообще не беспокоился (и ваши опасения по поводу производительности практически неоправданны).


переключение контекста — это концепция, связанная с операционная система и многозадачность и происходит в процессах, запущенных машинный код, исполняемый во время вытеснения. Как этот исполняемый файл получается (из компилятора C, из компилятора C++, из компилятора Go, из компилятора SBCL или быть интерпретатором какого-либо другого языка, такого как Perl или байт-код Python) совершенно не имеет значения (поскольку переключение контекста может произойти в любой компьютерной инструкции во время прерывания). Прочтите несколько книг, например Операционные системы: три простых элемента. .

person Basile Starynkevitch    schedule 30.12.2017
comment
Я думаю, что OP предположил, что при переходе с C ++ на C и наоборот может произойти переключение стека. Иногда это требуется при переходе с одного языка на другой, хотя с C и C++ это не так. Go, вероятно, должен каким-то образом переключать стек при выполнении внешнего кода C из-за того, что он использует сегментированный стек для сопрограмм и, следовательно, требует вставки специально сгенерированного кода в каждую функцию, что не делается для типичной библиотеки C. - person StaceyGirl; 30.12.2017
comment
Насколько я знаю, в этом случае Go не переключает стек, но даже если бы это было так, это не было бы переключением контекста и работало бы очень быстро. - person Basile Starynkevitch; 30.12.2017
comment
@BasileStarynkevitch Это в основном переключатель контекста пользовательского пространства. Это намного быстрее, чем переключение контекста ОС. Я не знаю, делает ли это Go, но он должен каким-то образом обрабатывать переполнение стека во внешнем коде C. Так что это либо сбой, либо переключение стека, или, может быть, что-то среднее ..? - person StaceyGirl; 30.12.2017
comment
Он поставил переключение контекста в кавычки. Я предположил, что он имел в виду какой-то thunking/прокси-код между программой C++ и библиотекой C. - person Steve; 30.12.2017
comment
Обработка стеков Go описана здесь: blog.cloudflare.com/how- стеки обрабатываются на ходу. Критические части среды выполнения Go, такие как GC, выполняются в отдельном стеке. И они возвращаются к сегментированному стеку для произвольного кода C. Похоже, они не могут надежно выполнять код C. Я ожидаю, что код C приведет к сбою процесса, если что-то пойдет не так и стек 8 КБ переполнится. - person StaceyGirl; 30.12.2017
comment
Но изменение стека происходит очень быстро, просто загружая указатель стека. Управление стеками — это другой вопрос. Теоретически с помощью GCC вы могли бы скомпилировать свой код C с помощью -fsplit-stack, чтобы получить одинаковое поведение в коде C и в коде Go, но никто этого не делает. - person Basile Starynkevitch; 30.12.2017
comment
@BasileStarynkevitch Не уверен, сколько информации им нужно, но коду, выполняющемуся в отдельном стеке, нужна некоторая информация, чтобы иметь возможность вернуться, поэтому требуется некоторая дополнительная работа. Может быть половина переключения контекста. - person StaceyGirl; 30.12.2017
comment
например, несколько (но не все) реализаций ::operator new просто вызывают malloc, но не являются встроенными. Это может быть намного хуже: на моей машине ::operator new(), реализованная для вызова malloc(), превосходит стандартную ::operator new() примерно на сто циклов процессора. за звонок... - person cmaster - reinstate monica; 30.12.2017
comment
longjmp может быть быстрее, когда переход/исключение действительно происходит, но механизм генерации исключений C++ почти наверняка быстрее, когда исключения не происходит (т. е. ему не нужен setjmp и он часто свободен или почти свободен). - person BeeOnRope; 30.12.2017

На базовом уровне, нет, вы не увидите никакого снижения производительности при "переключении" при вызове библиотеки C из кода C++. Например, вызов из C++ метода C, определенного в другой единице трансляции, должен иметь приблизительно такую ​​же производительность, что и вызов того же метода, реализованного в C++ (таким же образом, как в C) в другой единице трансляции.

Это связано с тем, что распространенные реализации компиляторов C и C++ в конечном итоге компилируют исходный код в машинный код, а вызов функции extern "C" эффективно поддерживается с использованием того же типа call, что и для вызова C++. Соглашения о вызовах обычно основаны на платформе ABI и одинаковы в любом случае.

Помимо этого основного факта, при вызове функции C все еще могут быть некоторые недостатки производительности, в отличие от реализации той же функции в C++:

  • Функции, реализованные в C и объявленные extern "C" и вызываемые из кода C++, обычно не будут встроены (поскольку по определению они не реализованы в заголовке), что препятствует целому ряду, возможно, очень мощной оптимизации0 .
  • Большинство типов данных, используемых в коде C++1, нельзя напрямую использовать в коде C, поэтому, например, если у вас есть std::string в коде C++, вам нужно будет выбрать другой тип для его передачи. в код C - char * распространен, но теряет информацию о явной длине, что может быть медленнее, чем решение C++. Многие типы не имеют прямого эквивалента в C, поэтому вам может понадобиться дорогостоящее преобразование.
  • Код C использует malloc и free для динамического управления памятью, в то время как код C++ обычно использует new и delete (и обычно предпочитает максимально скрывать эти вызовы за другими классами). Если вам нужно выделить память на одном языке, которая будет освобождена на другом другом, это может привести к несоответствию, когда вам нужно перезвонить на «другой» язык, чтобы освободить, или могут быть ненужные копии и т. д.
  • В коде C часто интенсивно используются подпрограммы стандартной библиотеки C, а в коде C++ обычно используются методы из стандартной библиотеки C++. Поскольку существует много функциональных совпадений, возможно, что смесь C и C++ занимает больше кода, чем чистый код C++, поскольку используется больше методов библиотеки C2.

Вышеупомянутые опасения применимы только при сравнении реализации на чистом C++ с реализацией на C, и на самом деле не означают, что при вызове C происходит снижение производительности: на самом деле это ответ на вопрос: «Почему можно написать приложение на смеси C и C? С++ медленнее, чем чистый С++?». Кроме того, вышеперечисленные проблемы в основном возникают при очень коротких вызовах, когда вышеуказанные накладные расходы могут быть значительными. Если вы вызываете длинную функцию на C, это не проблема. "Несоответствие типов данных" может по-прежнему кусать вас, но это можно спроектировать на стороне C++.


0 Интересно, что оптимизация времени компоновки фактически позволяет методам C быть встроенными в код C++, что мало упоминаемое преимущество LTO. Конечно, обычно это зависит от самостоятельной сборки библиотеки C из исходного кода с соответствующими параметрами LTO.

1 Например, почти все, кроме стандартного макета.

2 Это, по крайней мере, частично смягчается тем фактом, что многие вызовы стандартной библиотеки C++ в конечном итоге делегируют подпрограммы библиотеки C для «тяжелой» работы, например, как std::copy вызывает memcpy или memset, когда это возможно, и как большинство new реализации в конечном итоге вызывают malloc.

person BeeOnRope    schedule 30.12.2017

C++ вырос и сильно изменился с момента своего создания, но по своей конструкции он обратно совместим с C. Компиляторы C++ обычно строятся на основе компиляторов C, но еще более модернизируются с помощью оптимизация времени компоновки. Я полагаю, что многие программы могут надежно смешивать код C и C++ как в пользовательском пространстве, так и в используемых библиотеках. Недавно я ответил на вопрос, что включает передачу указателя функции-члена класса C++ в библиотечную функцию, реализованную на языке C. Плакат сказал, что это сработало для него. Так что, возможно, C++ более совместим с C, чем думают программисты или пользователи.

Однако C++ работает во многих различных парадигмах, которых нет в C, поскольку он является объектно-ориентированным и реализует целый спектр абстракций, новых типов данных и операторов. Некоторые типы данных легко переводятся (строка char * C в std::string), а другие нет. Этот раздел GNU.org о параметрах компилятора C++ может быть какой-то интерес.

Я бы не слишком беспокоился или беспокоился о каком-либо снижении производительности при смешивании двух языков. Конечный пользователь и даже программист вряд ли заметят какие-либо измеримые изменения в производительности, если только они не имеют дело с большими абстракциями данных.

person Tanner Babcock    schedule 30.12.2017
comment
Ваша ссылка на параметры компилятора GCC ужасно устарела (поскольку GCC3.0 устарел уже давно). Используйте gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html - person Basile Starynkevitch; 30.12.2017