Telegram Group & Telegram Channel
#dev #cpp #rust

Есть баян, что вот в таком коде функция в качестве коллбека менее эффективна, чем лямбда:

#include <cstdio>

__attribute__((noinline))
void invoke_callback(auto callback) {
callback();
}

void my_callback() {
puts("invoke via function");
}

int main() {
invoke_callback(my_callback);

invoke_callback([]() {
puts("invoke via lambda");
});
}


Ларчик открывается просто: my_callack мгновенно превращается в указатель на функцию, и invoke_callback приходится делать непрямой вызов по указателю. Лямбда же является уникальным типом с фиксированным перегруженным operator(), поэтому коллбек спокойно инлайнится. Не менее баян, что в Rust это не проблема, и

#[inline(never)]
fn invoke_callback(callback: impl FnOnce()) {
callback();
}

fn my_callback() {
println!("invoke via function");
}

#[no_mangle]
fn main() {
invoke_callback(my_callback);

invoke_callback(|| {
println!("invoke via lambda");
});
}


работает эффективно в обоих случаях, ведь в Rust функции являются не указателями, а анонимными ZST-типами, прям как лямбды. А вот вам не баян: оказывается, если заменить вызов invoke_callback на invoke_callback(my_callback as fn()), чтобы произошел вызов по указателю, производительность не ухудшится! Почему?

Здесь происходит нечто в духе девиртуализации. При интер-процедурном анализе компилятор видит, что аргумент invoke_callback<fn()> — это всегда указатель на my_callback, поэтому callback() можно оптимизировать до my_callback().

Почему Clang не может сделать то же самое, ведь он тоже использует LLVM? Может, если invoke_callback сделать статической функцией (GCC тоже делает девиртуализацию, но потом не инлайнит вызов). Функции без static же видны из других единицах трансляции, поэтому вполне возможно, что кто-нибудь "снаружи" вызовет invoke_callback<void (*)()> с аргументом помимо my_callback, и в этом случае оптимизация неприменима.

Естественно, как только начинают использоваться два разных коллбека, девиртуализация ломается, так что использовать ZST-типы вместо указателей на функции все еще полезно.

Забавно, что даже то, что invoke_callback — шаблонная функция, не помогает, хотя, казалось бы, раз каждый cpp-файл #include'ит в себя заголовок с реализацией шаблонной функции, то проводить оптимизацию в рамках единицы трансляции корректно. Это не так по двум причинам.

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

Во-вторых, шаблонная функция может быть определена в одной единице трансляции, а затем использоваться в другой без реализации:

// a.cpp
#include <iostream>

template<typename T>
void print(T value) {
std::cout << value;
}

template void print<int>(int value);
template void print<const char*>(const char* value);

// main.cpp
template<typename T>
void print(T value);

int main() {
print(2);
print(" + ");
print(2);
print(" = ");
print(4);
print("\n");
}


Конструкция с template без угловых скобок инстанциирует шаблонную функцию для данного T, и другие единицы линковки потом могут к этим инстансам линковаться. (Попытка использовать в main.cpp print с неинстанциированными типами при этом приведет к ошибке линковки.)



group-telegram.com/alisa_rummages/206
Create:
Last Update:

#dev #cpp #rust

Есть баян, что вот в таком коде функция в качестве коллбека менее эффективна, чем лямбда:

#include <cstdio>

__attribute__((noinline))
void invoke_callback(auto callback) {
callback();
}

void my_callback() {
puts("invoke via function");
}

int main() {
invoke_callback(my_callback);

invoke_callback([]() {
puts("invoke via lambda");
});
}


Ларчик открывается просто: my_callack мгновенно превращается в указатель на функцию, и invoke_callback приходится делать непрямой вызов по указателю. Лямбда же является уникальным типом с фиксированным перегруженным operator(), поэтому коллбек спокойно инлайнится. Не менее баян, что в Rust это не проблема, и

#[inline(never)]
fn invoke_callback(callback: impl FnOnce()) {
callback();
}

fn my_callback() {
println!("invoke via function");
}

#[no_mangle]
fn main() {
invoke_callback(my_callback);

invoke_callback(|| {
println!("invoke via lambda");
});
}


работает эффективно в обоих случаях, ведь в Rust функции являются не указателями, а анонимными ZST-типами, прям как лямбды. А вот вам не баян: оказывается, если заменить вызов invoke_callback на invoke_callback(my_callback as fn()), чтобы произошел вызов по указателю, производительность не ухудшится! Почему?

Здесь происходит нечто в духе девиртуализации. При интер-процедурном анализе компилятор видит, что аргумент invoke_callback<fn()> — это всегда указатель на my_callback, поэтому callback() можно оптимизировать до my_callback().

Почему Clang не может сделать то же самое, ведь он тоже использует LLVM? Может, если invoke_callback сделать статической функцией (GCC тоже делает девиртуализацию, но потом не инлайнит вызов). Функции без static же видны из других единицах трансляции, поэтому вполне возможно, что кто-нибудь "снаружи" вызовет invoke_callback<void (*)()> с аргументом помимо my_callback, и в этом случае оптимизация неприменима.

Естественно, как только начинают использоваться два разных коллбека, девиртуализация ломается, так что использовать ZST-типы вместо указателей на функции все еще полезно.

Забавно, что даже то, что invoke_callback — шаблонная функция, не помогает, хотя, казалось бы, раз каждый cpp-файл #include'ит в себя заголовок с реализацией шаблонной функции, то проводить оптимизацию в рамках единицы трансляции корректно. Это не так по двум причинам.

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

Во-вторых, шаблонная функция может быть определена в одной единице трансляции, а затем использоваться в другой без реализации:

// a.cpp
#include <iostream>

template<typename T>
void print(T value) {
std::cout << value;
}

template void print<int>(int value);
template void print<const char*>(const char* value);

// main.cpp
template<typename T>
void print(T value);

int main() {
print(2);
print(" + ");
print(2);
print(" = ");
print(4);
print("\n");
}


Конструкция с template без угловых скобок инстанциирует шаблонную функцию для данного T, и другие единицы линковки потом могут к этим инстансам линковаться. (Попытка использовать в main.cpp print с неинстанциированными типами при этом приведет к ошибке линковки.)

BY Алиса копается


Warning: Undefined variable $i in /var/www/group-telegram/post.php on line 260

Share with your friend now:
group-telegram.com/alisa_rummages/206

View MORE
Open in Telegram


Telegram | DID YOU KNOW?

Date: |

Meanwhile, a completely redesigned attachment menu appears when sending multiple photos or vides. Users can tap "X selected" (X being the number of items) at the top of the panel to preview how the album will look in the chat when it's sent, as well as rearrange or remove selected media. And indeed, volatility has been a hallmark of the market environment so far in 2022, with the S&P 500 still down more than 10% for the year-to-date after first sliding into a correction last month. The CBOE Volatility Index, or VIX, has held at a lofty level of more than 30. Telegram was founded in 2013 by two Russian brothers, Nikolai and Pavel Durov. In addition, Telegram's architecture limits the ability to slow the spread of false information: the lack of a central public feed, and the fact that comments are easily disabled in channels, reduce the space for public pushback. On Telegram’s website, it says that Pavel Durov “supports Telegram financially and ideologically while Nikolai (Duvov)’s input is technological.” Currently, the Telegram team is based in Dubai, having moved around from Berlin, London and Singapore after departing Russia. Meanwhile, the company which owns Telegram is registered in the British Virgin Islands.
from us


Telegram Алиса копается
FROM American