По поводу ebp я с одной стороны понял, а с другой не понял.
Понял, что при использовании исключений он может заниматься, и т.о. останется меньше регистров для вычислений.
Правда, имхо, такой эффект будет нетривиально отследить — надо сделать какой-то синтетический тест, которому требуется много регистров...
достаточно простенький пример и ebp _не_ занимается.
Почему и под что он будет заниматься в более сложных случаях?
Я бы понял, если бы наоборот — в простых он занимался, а в сложных освобождался каким-либо образом под другие нужны, а наоборот не понимаю...
Заодно по поводу thiscall. Для С случая нельзя использовать thiscall — его там нет. Поэтому всё же придётся мерить с cdecl.
Случай, когда в С++ используются возвращаемые значения, конечно, можно и надо мерить с thiscall.
По поводу сделать для случая С++ с возвращаемыми значениями вместо деструктора функцию deinit() — так не пойдёт. Даже если в С++ и используют функции типа init/create и т.д. вместо конструктора, то деструкторы используют даже самые вшивинькие библиотеки типа MFC/ACE. Поэтому я считаю, что случай с deinit() в С++ просто doesn't make any sense.
R>По поводу ebp я с одной стороны понял, а с другой не понял.
R>Понял, что при использовании исключений он может заниматься, и т.о. останется меньше регистров для вычислений. R>Правда, имхо, такой эффект будет нетривиально отследить — надо сделать какой-то синтетический тест, которому требуется много регистров...
Эффект проявляется уже в простейших нагруженых функциях. Как пример — недавно делал блиттер. Так вот, этот несчастный один дополнительный регистр во внутреннем цикле давал +20% производительности. Ну мало регистров у х86, никуда от этого не уйти... Идиотизм, конечно, но тут все вопросы к Интел.
R>Не понял, когда и под что он занимается. R>здесь
достаточно простенький пример и ebp _не_ занимается. R>Почему и под что он будет заниматься в более сложных случаях?
Я привел код. Посмотри, там явно видно, под что.
R>Я бы понял, если бы наоборот — в простых он занимался, а в сложных освобождался каким-либо образом под другие нужны, а наоборот не понимаю...
Это как это наоборот? В простейших случаях компилятор может организовать вычисления стек-фрейма на esp, а в сложных ему это сделать не судьба — когда стек фрейм может раскручиваться как локальным исключением (фрейм, установленный в самой функции), так и во фрейм, установленный по вызову выше. В общем, ничего удивительного.
R>Заодно по поводу thiscall. Для С случая нельзя использовать thiscall — его там нет. Поэтому всё же придётся мерить с cdecl.
Случай "С" меня как то не очень интересует, откровенно говоря. Надо сравнивать сравнимое.
R>Случай, когда в С++ используются возвращаемые значения, конечно, можно и надо мерить с thiscall.
R>По поводу сделать для случая С++ с возвращаемыми значениями вместо деструктора функцию deinit() — так не пойдёт. Даже если в С++ и используют функции типа init/create и т.д. вместо конструктора, то деструкторы используют даже самые вшивинькие библиотеки типа MFC/ACE. Поэтому я считаю, что случай с deinit() в С++ просто doesn't make any sense.
Нет. Обычно init\destroy используют, когда имеются тривиальные конструкторы\деструкторы (которые инлайнятся), и тяжелые функции первичной\вторичной инициализации. Либо вообще не имеют конструкторов\деструкторов — получаются обычные POD. Поэтому именно то, что я приводил. А назвать MFC или ACE вшивенькой библиотекой — ну это да. Вообще, MFC как раз очень вполне завязана на исключения — посмотреть те же самые CFile.
Здравствуйте, remark, Вы писали:
R>В общем случае, если код делает одно и тоже, то и машинный код быдет таким же. Не виже никаких причин для обратного.
auto_ptr делает несколько больше. Я же не писал "всегда отличается"
R>Ты не поверишь, но компилятор сделал следующее:
R>
Поверю, у меня тела функций тоже получились идентичными, но в разных местах, поэтому хотелось бы узнать компилятор и ключи
Немного модернизировал пример, что бы было лучше видно лишнюю работу:
__declspec(noinline) void auto_ptr()
{
std::auto_ptr<int> i /*(new int(5))*/;
}
>__declspec(noinline) void by_hand()
{
int* i /*= new int(5)*/;
//delete i;
}
int main()
{
auto_ptr();
by_hand();
}
MSVC 2005 + Dinkumware STL:
; Function compile flags: /Ogspy
?by_hand@@YAXXZ PROC ; by_hand
ret 0
?by_hand@@YAXXZ ENDP ; by_hand
?auto_ptr@@YAXXZ PROC ; auto_ptr
push 0
call ??3@YAXPAX@Z ; operator delete
pop ecx
ret 0
?auto_ptr@@YAXXZ ENDP ; auto_ptr
_main PROC
call ?auto_ptr@@YAXXZ ; auto_ptr
xor eax, eax
ret 0
_main ENDP
Конечно вырожденный случай, вот более жизненный:
__declspec(noinline) void auto_ptr()
{
std::auto_ptr<int> i;
int* i2 = new int(5);
i.reset(i2);
}
__declspec(noinline) void by_hand()
{
int* i;
int* i2 = new int(5);
i = i2;
delete i;
}
int main()
{
auto_ptr();
by_hand();
}
MSVC 2005 + Dinkumware STL:
; Function compile flags: /Ogspy
?by_hand@@YAXXZ PROC ; by_hand
push 4
call ??2@YAPAXI@Z ; operator new
test eax, eax
pop ecx
je SHORT $LN3@by_hand
mov DWORD PTR [eax], 5
jmp SHORT $LN4@by_hand
$LN3@by_hand:
xor eax, eax
$LN4@by_hand:
push eax
call ??3@YAXPAX@Z ; operator delete
pop ecx
ret 0
?by_hand@@YAXXZ ENDP ; by_hand
?auto_ptr@@YAXXZ PROC ; auto_ptr
push esi
push 4
call ??2@YAPAXI@Z ; operator new
test eax, eax
pop ecx
je SHORT $LN3@auto_ptr
mov DWORD PTR [eax], 5
mov esi, eax
jmp SHORT $LN4@auto_ptr
$LN3@auto_ptr:
xor esi, esi
$LN4@auto_ptr:
test esi, esi
je SHORT $LN7@auto_ptr
push 0
call ??3@YAXPAX@Z ; operator delete
pop ecx
$LN7@auto_ptr:
push esi
call ??3@YAXPAX@Z ; operator delete
pop ecx
pop esi
ret 0
?auto_ptr@@YAXXZ ENDP; auto_ptr
В STLPort должны получиться похожие результаты.
Cитуацию (точнее, auto_ptr) можно немного исправить.
первый вариант:
; Function compile flags: /Ogspy
?auto_ptr@@YGXXZ PROC ; auto_ptr, COMDAT
ret 0
?auto_ptr2@@YGXXZ ENDP; auto_ptr
второй:
; Function compile flags: /Ogspy
; COMDAT ?by_hand@@YGXXZ
_TEXT SEGMENT
?by_hand@@YGXXZ PROC ; by_hand, COMDAT
; заинлайнен new:
push esi
mov esi, 1280593503 ; 4c544e5fH
push esi
push 4
push 1
call DWORD PTR __imp__ExAllocatePoolWithTag@12test eax, eax
je SHORT $LN3@by_hand
mov DWORD PTR [eax], 5
jmp SHORT $LN4@by_hand
$LN3@by_hand:
xor eax, eax
$LN4@by_hand:
test eax, eax
je SHORT $LN12@by_hand
; заинлайнен delete
push esi
push eax
call DWORD PTR __imp__ExFreePoolWithTag@8
$LN12@by_hand:
pop esi
ret 0
?by_hand@@YGXXZ ENDP ; by_hand
?auto_ptr@@YGXXZ PROC ; auto_ptr, COMDAT
; заинлайнен new:
push esi
mov esi, 1280593503 ; 4c544e5fH
push esi
push 4
push 1
call DWORD PTR __imp__ExAllocatePoolWithTag@12test eax, eax
je SHORT $LN3@auto_ptr
mov DWORD PTR [eax], 5
jmp SHORT $LN23@auto_ptr
$LN3@auto_ptr:
xor eax, eax
$LN23@auto_ptr:
test eax, eax
je SHORT $LN37@auto_ptr
; заинлайнен delete
push esi
push eax
call DWORD PTR __imp__ExFreePoolWithTag@8
$LN37@auto_ptr:
pop esi
ret 0
?auto_ptr@@YGXXZ ENDP; auto_ptr
Текст примеров не изменён, исходный текст auto_ptr приведу следующим постом, этот и так уже большой
R>Или вот ещё:
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
R>Речь идёт о замере интервалов порядка десятков тактов.
Десятков???? Десятков тысяч. Или ты про разницу?
R>Я предлагаю следующую методику: R>Выполняем код много раз подряд. R>Разы, в которые произошёл вызов планировщика, переключение контекста, страничное прерывание и т.д. отбрасываем, как выдающие заведомо оооочень большое неправильное время, никак не связанное с нашим измеряемым кодом. R>Т.о. получаем "истинное" время выполнения целевого фрагмента кода.
Планировщик вызывается раз в ~15мс. Ты действительно думаешь, что он не влияет на результаты замеров? Еще раз — чтобы снизить влияние планировщика, надо поднять приоритеты и увеличить время замеров до нескольких (десятков) минут.
R>Ты предлагаешь (поправь, где не прав): R>Выполняем код много раз подряд.
Много _тысяч_ раз подряд.
R>Разы, в которые произошёл вызов планировщика, переключение контекста, страничное прерывание и т.д. учитываем.
Мы их в любом случае учитываем. Ты в мультизадачной ОС. Прервать выполнение кода может не только планировщик — еще есть аппаратные прерывания, DMA, и многие другие вещи, неявно влияющие на производительность памяти, процессора и т.п.
R>Потом "размазываем" эти ооочень сильно большие времена, усредняя все значения.
Нет. Времена будут примерно одинаковые. Почитай статью по ссылке, там пояснено, как что и почему. Я уже устал говорить, что время выполнения в современных программно-аппаратных системах зависит не только от программной, но и той аппаратной части (юнита), на котором исполняется код. А с учетом всяких "прикольных" чипсетов типа KT133 с его закидонами с шиной — так и вообще от фазы луны. Поэтому надо использовать длительные измерения, а влияние перепланировки уменьшать путем повышения приоритета. Право — это же азы.
R>Т.о. получаем "истинное" время выполнения целевого фрагмента кода.
Не истинное, а реальное. Минимальное время выполнения на современном железе может быть вызвано совершенно безумным сочетанием невоспроизводимых в реальной ситуации факторов (ну или редко воспроизводимых).
R>Т.е. ты считаешь нормальным, если, например, при замере одного фрагмента кода переключения контекста произошли 1 раз, а во время замера второго фрагмента кода переключения контекста произошли 2 раза. Потом мы усредняем эти времена и сравниваем.
Нет. Я считаю нормальным, когда за время замера переключение контекста произошло 100000 или 100010 раз.
R>Т.е. ты считаешь нормальным, если произошёл вызов планировщика, потом одно переключение контекста, потом поработал другой поток, потом опять вызов планировщика, потом опять переключение контекста и потом продолжила работать наша функция. Это нормально, это время можно включить в замер функции в 20 тактов. А вот если ко всему этому прибавляется ещё и пенальти, вызванное переключением потока на другое ядро, то вот это ты считаешь не нормальным, и для борьбы с этим предлагаешь делать привязку потока к ядру.
Ты все в кашу мешаешь... Даже не буду комментировать, но право, рекомендую разобраться, что к чему и зачем. К одному ядру я рекомендую привязывать по совершенно другим причинам. И я о них уже говорил.
Ты, например, даже не учитываешь, что RDTSC замеряет совсем не время выполнения кода, а совершенно другое — пропускную способность декодера команд. Поэтому все твои измерения с точностью до 20 команд — пшик. Конвейер у того же П4 — 18-20 стадий, что ты меряешь? Почитай статью — там хороший пример. ПОэтому надо мерить не 20 тактов, а 200000 тактов. Тогда точность измерения будет приемлемой (инструмент измерений не будет влиять на измеряемый объект). Между вызовами точек отсчета должны проходить десятки, а лучше сотни тысяч команд.
R>Аналогичная ситуация с поднятием приоритета потока. Ты считаешь, что вызов планировщика совершенно нормально учитывать при замере куска кода.
Без этого — никак. Хочешь отключить планировщик? Работай в DOS
R>Ну уж извините, с таким подходом я в корне не согласен. Хоть убейте.
Ну, можно не соглашаться с теорией относительности, или с тем, что в одном рубле 100 копеек. Но от этого она (ТО) не станет более верной или не верной, а рубль не полегчает и тяжелее тоже не станет. Есть испытанные методики измерения времени выполнения кода. Про них тебе уже говорили несколько разных людей, не только я.
Все, эта тема для меня закрыта — нового я тебе ничего не скажу, ты мне — тоже . Право, ходим по кругу — я не вижу смысла продолжать.
Здравствуйте, Аноним, Вы писали:
А>То, что исключения позволяют сократить один if при вызове функции, по-моему, с лихвой компенсируется тем, что исключения вынуждают пользоваться обертками в виде умных указателей.
"Вы поставили знак минус вместо знака плюс"
Выделенное нужно заменить на и бесплатно получаем
Уже ради этих обеих вещей стоит использовать исключения