2013-11-13 2 views
1

Предполагая ptr является указатель на объект типа T1 и inst является экземпляром типа T2:Различия в управлении памятью с помощью этих 2 вызовов функций?

T1* ptr(new T1); 
T2 inst; 

Я разработать методы T1 и T2 соответственно, а это означает, что в T1 я довольно много имеют только void функции который будет работать на объекте и внутри T2 У меня будут методы, которые будут иметь доступ к фактическим членам. Так что я, наконец, сделать 2 звонков, как так:

ptr->doSomething(); 
inst.doSomething(); 

Учитывая эти 2 основных отличия (указатели против экземпляра и фактический вызов -> против .) и возможно использованием this против member values, в многопоточном и высокопроизводительных окружающая среда, модель памяти, наложенная на ptr и inst - это то же самое? Как насчет стоимости переключения контекста, создания/распределения стека, доступа к значению и т. Д.?

EDIT:

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

Я хотел бы сфокусировать это на модели памяти, на том, как вещи работают внутри аппаратного обеспечения (в основном, x86 и ARM).

+1

Обратите внимание, что оба используют 'this' внутренне, явно или неявно. – juanchopanza

+2

Примечание: ваш второй «экземпляр» * не *; это объявление функции для функции под названием «inst», возвращающей значение объекта типа «T2» и не принимающее никаких параметров. Я думаю, что вы имели в виду 'T2 inst;' – WhozCraig

+2

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

ответ

3

Похоже, ваш вопрос просто: В чем разница между вызовом «ptr-> что-то() "и" instance.something()? "

С точки зрения функции «что-то», абсолютно ничего.

#include <iostream> 

struct Foo { 
    void Bar(int i) { std::cout << i << "\n"; } 
}; 

int main() { 
    Foo concrete; 
    Foo* dynamic = new Foo; 

    concrete.Bar(1); 
    dynamic->Bar(2); 

    delete dynamic; 
} 

Компилятор излучает только один экземпляр Foo :: Bar(), который должен обрабатывать оба случая, так что не может быть какая-то разница.

Только изменения, если они есть, находятся на сайте вызова. При вызове dynamic->Bar() компилятор будет испускать код, эквивалентный this = dynamic; call Foo0Bar, для передачи значения «динамический» непосредственно в месте, где «это» удерживается (регистр/адрес). В случае concrete.Bar бетон будет находиться в стеке, поэтому он выдает немного другой код для загрузки смещения стека в один и тот же регистр/ячейку памяти и выполняет вызов. Сама функция не сможет сказать.

---- EDIT ----

Вот сборка из "г ++ -Wall -o TEST.exe -O1 test.cpp & & objdump -LSD TEST.exe | C++ фильт" с приведенный выше код, сосредоточившись на главной:

main(): 
    400890:  53      push %rbx 
    400891:  48 83 ec 10    sub $0x10,%rsp 
    400895:  bf 01 00 00 00   mov $0x1,%edi 
    40089a:  e8 f1 fe ff ff   callq 400790 <operator new(unsigned long)@plt> 
    40089f:  48 89 c3    mov %rax,%rbx 
    4008a2:  be 01 00 00 00   mov $0x1,%esi 
    4008a7:  48 8d 7c 24 0f   lea 0xf(%rsp),%rdi 
    4008ac:  e8 47 00 00 00   callq 4008f8 <Foo::Bar(int)> 
    4008b1:  be 02 00 00 00   mov $0x2,%esi 
    4008b6:  48 89 df    mov %rbx,%rdi 
    4008b9:  e8 3a 00 00 00   callq 4008f8 <Foo::Bar(int)> 
    4008be:  48 89 df    mov %rbx,%rdi 
    4008c1:  e8 6a fe ff ff   callq 400730 <operator delete(void*)@plt> 
    4008c6:  b8 00 00 00 00   mov $0x0,%eax 
    4008cb:  48 83 c4 10    add $0x10,%rsp 
    4008cf:  5b      pop %rbx 
    4008d0:  c3      retq 

Наши вызовы функций члена здесь:

concrete.Bar (1)

4008a2:  be 01 00 00 00   mov $0x1,%esi 
4008a7:  48 8d 7c 24 0f   lea 0xf(%rsp),%rdi 
4008ac:  e8 47 00 00 00   callq 4008f8 <Foo::Bar(int)> 

dynamic-> Бар (2)

4008b1:  be 02 00 00 00   mov $0x2,%esi 
4008b6:  48 89 df    mov %rbx,%rdi 
4008b9:  e8 3a 00 00 00   callq 4008f8 <Foo::Bar(int)> 

Ясно, что «RDI» используется для того чтобы держать «этот», и первый использует стек-относительный адрес (с concrete находится в стеке), а второй просто копирует значение «RBX», который имеет возвращаемое значение из «нового» ранее (mov %rax,%rbx после вызова нового)

---- EDIT 2 ----

Помимо вызова функции сам, говоря с фактическими операциями, которые должны произойти при построении, срывании и доступе-значениях внутри объектов, стек обычно быстрее.

{ 
    Foo concrete; 
    foo.Bar(1); 
} 

обычно занимает меньше циклов, чем

Foo* dynamic = new Foo; 
dynamic->Bar(1); 
delete dynamic; 

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

Другим потенциальным преимуществом использования стека является общая когерентность кэширования.

int i, j, k; 
Foo f1, f2, f3; 
// ... thousands of operations populating those values 
f1.DoCrazyMagic(f1, f2, f3, i, j, k); 

Если нет внешних ссылок внутри DoCrazyMagic, то все операции будут происходить в пределах небольшого населенного пункта памяти. И наоборот, если мы делаем

int *i, *j, *k; 
Foo *f1, *f2, *f3; 
// ... thousands of operations populating those values 
f1->DoCrazyMagic(*f1, *f2, *f3, *i, *j, *k); 

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

Однако, если «тысячи операций» являются достаточно интенсивными и сложными, область стека, где мы помещаем i, j, k, f1, f2 and f3, может быть не более «горячей».

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

+0

+1 только для добавления - стек несколько быстрее, куча больше, выбор между ними - это использование и вне сферы действия –

+0

@GlennTeitelbaum Хорошая точка - любая обратная связь на edit2? [примечание: я только что увидел «и вне сферы видимости», хех] – kfsone

+1

сохранить этот пост и повторно использовать его, когда кто-то четко задает вопрос простым и удобным способом поиска –

0

Основное различие между двумя экземплярами связано с временем жизни объекта.

T1 имеет динамическое распределение, то есть его срок службы заканчивается при вызове delete, T2 имеет автоматическое выделение, то есть его срок службы заканчивается, когда выполнение выходит из закрывающего блока выделения.

При выборе между динамическими или автоматическими переменными основным фактором принятия решения должно стать время жизни объекта.

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

Далеким третьим фактором может быть местность ссылок, что может означать в некоторых сценариях, что косвенность (->) наложит минутный штраф за выполнение. Это может сказать только профайлер.

Я разрабатываю методы T1 и T2 соответственно, что означает, что в T1 я в значительной степени только недействительные функции, которые будут работать на этом объекта и внутри Т2 я буду иметь методы, которые получат доступ фактические пользователей ,

Это не имеет особого смысла. Оба класса могут иметь члены и непустые функции.

Обратите внимание, что распределение динамической памяти налагает затраты, а также, как правило, блокировка должна быть приобретена внутри памяти распределителем памяти. Вы можете попробовать разные распределители (например, TCMalloc и другие), которые предлагают некоторые улучшения производительности в многопоточных сценариях.

С динамическим хранилищем также существует реальная резьба утечек памяти, забывая позвонить delete. Это можно смягчить, используя интеллектуальные указатели, но они добавляют свои собственные штрафы за производительность.

В целом, единственная реальная проблема в многопоточной среде или нет - это то, действительно ли вам нужны свойства (срок службы или размер), предлагаемые динамическим распределением, и готовы платить за ее стоимость.

(.. Экономически, что вы должны измерить, прежде чем принимать решение Совершенный враг достаточно хорошо)

+0

Блокировка памяти действительно необходима, только если значение указателя разделяется между несколькими потоками, если распределение, использование и удаление все происходит в одном потоке, тогда нет необходимости блокировать память. – Skizz

Смежные вопросы