Было несколько вопросов по схожим темам , но я не нашел ни одного вопроса, который бы исследовал варианты таким образом.
Часто нам нужно обернуть устаревший C -API в C ++, чтобы использовать его очень хорошую функциональность, защищая нас от капризов. Здесь мы сосредоточимся только на одном элементе. Как обернуть устаревшие C -функции, которые принимают char*
params. Пример спецификации c предназначен для API ( graphviz lib ), который принимает многие из его параметров как char*
без указания, является ли это const
или non-const
. Похоже, что нет попыток изменить, но мы не можем быть уверены на 100%.
Вариант использования для оболочки - это то, что мы хотим удобно вызывать оболочку C ++ с различными «строковыми» именами и значениями свойств, поэтому строковые литералы, строки, константные строки, string_views и т. Д. c. Мы хотим вызвать как по одному во время установки , когда производительность не критична , так и во внутреннем l oop, 100M + раз, где производительность имеет значение. (Код сравнения внизу)
Многие способы передачи «строк» в функции были объяснены в другом месте .
Приведенный ниже код тщательно прокомментирован для 4 опций функции cpp_wrapper()
, вызываемой 5 различными способами.
Какой самый лучший / самый безопасный / самый быстрый вариант? Это случай выбора 2?
#include <array>
#include <cassert>
#include <cstdio>
#include <string>
#include <string_view>
void legacy_c_api(char* s) {
// just for demo, we don't really know what's here.
// specifically we are not 100% sure if the code attempts to write
// to char*. It seems not, but the API is not `const char*` eventhough C
// supports that
std::puts(s);
}
// the "modern but hairy" option
void cpp_wrapper1(std::string_view sv) {
// 1. nasty const_cast. Does the legacy API modifY? It appears not but we
// don't know.
// 2. Is the string view '\0' terminated? our wrapper api can't tell
// so maybe an "assert" for debug build checks? nasty too?!
// our use cases below are all fine, but the API is "not safe": UB?!
assert((int)*(sv.data() + sv.size()) == 0);
legacy_c_api(const_cast<char*>(sv.data()));
}
void cpp_wrapper2(const std::string& str) {
// 1. nasty const_cast. Does the legacy API modifY? It appears not but we
// don't know. note that using .data() would not save the const_cast if the
// string is const
// 2. The standard says this is safe and null terminated std::string.c_str();
// we can pass a string literal but we can't pass a string_view to it =>
// logical!
legacy_c_api(const_cast<char*>(str.c_str()));
}
void cpp_wrapper3(std::string_view sv) {
// the slow and safe way. Guaranteed be '\0' terminated.
// is non-const so the legacy can modfify if it wishes => no const_cast
// slow copy? not necessarily if sv.size() < 16bytes => SBO on stack
auto str = std::string{sv};
legacy_c_api(str.data());
}
void cpp_wrapper4(std::string& str) {
// efficient api by making the proper strings in calling code
// but communicates the wrong thing altogether => effectively leaks the c-api
// to c++
legacy_c_api(str.data());
}
// std::array<std::string_view, N> is a good modern way to "store" a large array
// of "stringy" constants? they end up in .text of elf file (or equiv). They ARE
// '\0' terminated. Although the sv loses that info. Used in inner loop => 100M+
// lookups and calls to legacy_c_api;
static constexpr const auto sv_colours =
std::array<std::string_view, 3>{"color0", "color1", "color2"};
// instantiating these non-const strings seems wrong / a waste (there are about
// 500 small constants) potenial heap allocation in during static storage init?
// => exceptions cannot be caught... just the wrong model?
static auto str_colours =
std::array<std::string, 3>{"color0", "color1", "color2"};
int main() {
auto my_sv_colour = std::string_view{"my_sv_colour"};
auto my_str_colour = std::string{"my_str_colour"};
cpp_wrapper1(my_sv_colour);
cpp_wrapper1(my_str_colour);
cpp_wrapper1("literal_colour");
cpp_wrapper1(sv_colours[1]);
cpp_wrapper1(str_colours[2]);
// cpp_wrapper2(my_sv_colour); // compile error
cpp_wrapper2(my_str_colour);
cpp_wrapper2("literal_colour");
// cpp_wrapper2(colours[1]); // compile error
cpp_wrapper2(str_colours[2]);
cpp_wrapper3(my_sv_colour);
cpp_wrapper3(my_str_colour);
cpp_wrapper3("literal_colour");
cpp_wrapper3(sv_colours[1]);
cpp_wrapper3(str_colours[2]);
// cpp_wrapper4(my_sv_colour); // compile error
cpp_wrapper4(my_str_colour);
// cpp_wrapper4("literal_colour"); // compile error
// cpp_wrapper4(sv_colours[1]); // compile error
cpp_wrapper4(str_colours[2]);
}
Код теста
Пока не совсем реалистично c, поскольку работа в C -API минимальна и отсутствует в клиенте C ++. В полном приложении я знаю, что я могу сделать 10M в <1 с. Таким образом, простое переключение между этими двумя стилями абстракции API выглядит как изменение на 10%? Первые дни ... нужно больше работать. Примечание: это с короткой строкой, которая подходит для SBO. Более длинные с выделением кучи просто взорвать его полностью. </p>
#include <benchmark/benchmark.h>
static void do_not_optimize_away(void* p) {
asm volatile("" : : "g"(p) : "memory");
}
void legacy_c_api(char* s) {
// do at least something with the string
auto sum = std::accumulate(s, s+6, 0);
do_not_optimize_away(&sum);
}
// ... wrapper functions as above: I focused on 1&3 which seem
// "the best compromise".
// Then I added wrapper4 because there is an opportunity to use a
// different signature when in main app's tight loop.
void bench_cpp_wrapper1(benchmark::State& state) {
for (auto _: state) {
for (int i = 0; i< 100'000'000; ++i) cpp_wrapper1(sv_colours[1]);
}
}
BENCHMARK(bench_cpp_wrapper1);
void bench_cpp_wrapper3(benchmark::State& state) {
for (auto _: state) {
for (int i = 0; i< 100'000'000; ++i) cpp_wrapper3(sv_colours[1]);
}
}
BENCHMARK(bench_cpp_wrapper3);
void bench_cpp_wrapper4(benchmark::State& state) {
auto colour = std::string{"color1"};
for (auto _: state) {
for (int i = 0; i< 100'000'000; ++i) cpp_wrapper4(colour);
}
}
BENCHMARK(bench_cpp_wrapper4);
Результаты
-------------------------------------------------------------
Benchmark Time CPU Iterations
-------------------------------------------------------------
bench_cpp_wrapper1 58281636 ns 58264637 ns 11
bench_cpp_wrapper3 811620281 ns 811632488 ns 1
bench_cpp_wrapper4 147299439 ns 147300931 ns 5