Является ли этот «провал в праве» языком обязательным? - PullRequest
5 голосов
/ 23 марта 2020

Рассмотрим следующий код:

#include <utility>
#include <string>

int bar() {
    std::pair<int, std::string> p { 
        123, "Hey... no small-string optimization for me please!" };
    return p.first;
}

(упрощено благодаря @ Jarod42 :-) ...)

Я ожидаю, что функция будет реализована как просто:

bar():   
        mov eax, 123
        ret

но вместо этого реализация вызывает operator new(), создает std::string с моим литералом, затем вызывает operator delete(). По крайней мере - это то, что делают g cc 9 и clang 9 ( GodBolt ). Вот вывод clang:

bar():                                # @bar()
        push    rbx
        sub     rsp, 48
        mov     dword ptr [rsp + 8], 123
        lea     rax, [rsp + 32]
        mov     qword ptr [rsp + 16], rax
        mov     edi, 51
        call    operator new(unsigned long)
        mov     qword ptr [rsp + 16], rax
        mov     qword ptr [rsp + 32], 50
        movups  xmm0, xmmword ptr [rip + .L.str]
        movups  xmmword ptr [rax], xmm0
        movups  xmm0, xmmword ptr [rip + .L.str+16]
        movups  xmmword ptr [rax + 16], xmm0
        movups  xmm0, xmmword ptr [rip + .L.str+32]
        movups  xmmword ptr [rax + 32], xmm0
        mov     word ptr [rax + 48], 8549
        mov     qword ptr [rsp + 24], 50
        mov     byte ptr [rax + 50], 0
        mov     ebx, dword ptr [rsp + 8]
        mov     rdi, rax
        call    operator delete(void*)
        mov     eax, ebx
        add     rsp, 48
        pop     rbx
        ret
.L.str:
        .asciz  "Hey... no small-string optimization for me please!"

Мой вопрос: ясно, что компилятор полностью осведомлен обо всем, что происходит внутри bar(). Почему это не «исключение» / оптимизация строки прочь? Более конкретно:

  1. На базовом уровне c существует код между тогда new() и delete(), что, как знает AFAICT компилятор, ничего не дает.
  2. Во-вторых, new() и delete() звонят сами. В конце концов, стандартная оптимизация небольших строк разрешена стандартным AFAIK, так что даже если clang / g cc не решил использовать это - он может иметь; это означает, что на самом деле нет необходимости вызывать new() или delete() там.

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

Ответы [ 3 ]

3 голосов
/ 23 марта 2020

Ничто в вашем коде не представляет "elision", поскольку этот термин обычно используется в контексте C ++ . Компилятору не разрешается удалять что-либо из этого кода по признаку «elision».

Единственное основание, по которому компилятор должен удалить создание этой строки, основано на правиле «как будто». То есть, является ли поведение создания / уничтожения строки видимым для пользователя и, следовательно, не может быть удалено?

Поскольку оно использует std::allocator и стандартные черты характера, само конструирование и уничтожение basic_string не переопределяется пользователем. Таким образом, есть некоторая основа для идеи, что создание строки не является видимым побочным эффектом вызова функции и, следовательно, может быть удалено в соответствии с правилом «как будто».

Однако, поскольку указано std::allocator::allocate Чтобы вызвать ::operator new, а operator new является глобально заменяемым, разумно утверждать, что это является видимым побочным эффектом построения такой строки. И, следовательно, компилятор не может удалить его по правилу «как будто».

Если компилятор знает, что вы не заменили operator new, то теоретически он может оптимизировать эту строку.

Это не значит, что какой-то конкретный компилятор будет делать это.

2 голосов
/ 24 марта 2020

После обсуждения в различных ответах и ​​комментариях здесь, я подал следующие ошибки против G CC и LLVM по этой проблеме:

  1. G CC ошибка 94293 : [пропущенная оптимизация] new + удаление неиспользованной локальной строки не удалено

    Минимальный тестовый сценарий ( GodBolt ):

    void foo() {
        int *p = new int[1];
        *p = 42;
        delete[] p;
    }
    
  2. G CC ошибка 94294 : [пропущенная оптимизация] Бесполезные операторы, заполняющие локальную строку, не удалены

    Минимальный тестовый пример ( GodBolt ):

    void foo() {
        std::string s { "This is not a small string" };
    }
    
  3. Ошибка LLVM 45287 : [пропущенная оптимизация] ошибка удаления неиспользуемого libstdc ++ std :: string .

    Минимальный тестовый случай ( GodBolt ):

    void foo() {
        std::string s { "This is not a small string" };
    }
    

Благодарности: @JeffGarret, @NicolBolas, @ Jarod42, Mar c Glisse.

1 голос
/ 23 марта 2020

Вопрос в том, может ли программа

int bar() {
    std::pair<int, std::string> p { 
        123, "Hey... no small-string optimization for me please!" };
    return p.first;
}

быть действительно оптимизирована до

int bar() {
    return 123;
}

tldr, да, я думаю.

И clang работает с libc ++ : godbolt

О std::string, стандарт гласит string.require / 3

Каждый объект типа basic_string использует объект типа Allocator для выделения и освобождения хранилища для содержащихся объектов charT по мере необходимости.

«по мере необходимости». std::string разрешено решать, когда использовать распределитель (что, я считаю, оправдывает обоснованность единого входа). Его функции-члены не требуют распределения. Следовательно, распределение может быть исключено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...