Доступ по ссылочной накладной или копией - PullRequest
0 голосов
/ 07 февраля 2019

Допустим, я хочу передать объект POD в качестве аргумента const.Я знаю, что для простых типов, таких как int и double, передача по значению лучше, чем по const-ссылке из-за ссылочных издержек.Но какого размера стоит передать в качестве ссылки?

struct arg
{
  ...
}

void foo(const arg input)
{
  // read from input
}

или

void foo(const arg& input)
{
  // read from input
}

, т. Е. С какого размера struct arg мне следует начать использовать последний подход?

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

Ответы [ 3 ]

0 голосов
/ 07 февраля 2019

В дополнение к другим ответам, есть и проблемы оптимизации.

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

Например, если у вас есть значение if, зависящее от члена данных Foo,вызовите функцию, затем используйте тот же элемент данных, компилятор будет вынужден вывести две sparated загрузки, тогда как если переменная является локальной, она знает, что ее нельзя изменить в другом месте.Вот пример:

struct Foo {
    int data;
};

extern void use_data(int);

void bar(Foo const& foo) {
    int const& data = foo.data;

    // may mutate foo.data through a global Foo
    use_data(data);

    // must load foo.data again through the reference
    use_data(data);
}

Если переменная локальная, компилятор просто повторно использует значение, уже находящееся в регистрах.

Вот пример проводника компилятора , который показываетОптимизация применяется только в том случае, если переменная является локальной.

Вот почему «общие рекомендации» дадут вам хорошую производительность, но не дадут вам оптимальной производительности.Вы должны измерить и профилировать свой код, если вы действительно заботитесь о производительности своего кода.

0 голосов
/ 07 февраля 2019

TL; DR: Это сильно зависит от целевой архитектуры, компилятора и контекста, в котором вызываются функции.Если вы не уверены, профилируйте и вручную проверьте сгенерированный код.

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

Если функции неТем не менее, указывает на то, что ABI в большинстве реализаций C ++ предписывает передавать аргумент const& в качестве указателя.Это означает, что структура должна храниться в оперативной памяти, чтобы можно было получить ее адрес.Это может оказать значительное влияние на производительность для небольших объектов.

Давайте возьмем x86_64 Linux G ++ 8.2 в качестве примера ...

Структура с 2 членами:

struct arg
{
    int a;
    long b;
};

int foo1(const arg input)
{
    return input.a + input.b;
}

int foo2(const arg& input)
{
    return input.a + input.b;
}

Сгенерировано сборка :

foo1(arg):
        lea     eax, [rdi+rsi]
        ret
foo2(arg const&):
        mov     eax, DWORD PTR [rdi]
        add     eax, DWORD PTR [rdi+8]
        ret

Первая версия полностью передает структуру через регистры, вторая - через стек ..

Теперь давайте попробуем 3 члена :

struct arg
{
    int a;
    long b;
    int c;
};

int foo1(const arg input)
{
    return input.a + input.b + input.c;
}

int foo2(const arg& input)
{
    return input.a + input.b + input.c;
}

Сгенерировано сборка :

foo1(arg):
        mov     eax, DWORD PTR [rsp+8]
        add     eax, DWORD PTR [rsp+16]
        add     eax, DWORD PTR [rsp+24]
        ret
foo2(arg const&):
        mov     eax, DWORD PTR [rdi]
        add     eax, DWORD PTR [rdi+8]
        add     eax, DWORD PTR [rdi+16]
        ret

Не так уж много различийхотя использование второй версии все равно будет немного медленнее, потому что для этого требуется, чтобы адрес был введен в rdi.

Неужели так важно , что много?

Обычно нет.Если вы заботитесь о производительности определенной функции, она, вероятно, часто вызывается и поэтому составляет small .Таким образом, это, скорее всего, будет встроенный .

Давайте попробуем вызвать две функции выше:

int test(int x)
{
    arg a {x, x};
    return foo1(a) + foo2(a);
}

Генерируемая сборка:

test(int):
        lea     eax, [0+rdi*4]
        ret

Вуаля.Это все спорно в настоящее время.Компилятор встроил обе функции в одну инструкцию!

0 голосов
/ 07 февраля 2019

Эмпирическое правило: если размер класса такой же или меньше размера указателя, то копирование может быть немного быстрее.

Если размер класса немного выше, тоэто может быть трудно предсказать.Разница часто незначительна.

Если размер класса огромен, то копирование, скорее всего, будет медленнее.Тем не менее, точка является спорным, так как Humongous объекты не могут на практике иметь автоматическое хранение, так как оно ограничено.

1006 * Если функция расширяется инлайн, то, вероятно, никакой разницы. 1008 *Чтобы выяснить, работает ли одна программа быстрее, чем другая в конкретной системе, и является ли разница в первую очередь существенной, вы можете использовать профилировщик.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...