Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
К тому же, как вы сами сказали в посте выше: «как написать сортировку «наиболее правильным методом» на неуправляемый код — это действительно не такая простая задача, тут думать надо.», что еще раз подтверждает то что это очень спорная задача для С# разработчика.
Ваши основные тезисы, как мне кажется сводятся к тому, что мерить производительность не нужно, потому-что сложно учесть все и сделать максимально адекватный тест. Я же хочу ее измерить.
items[i] в C++ и C# означают разные вещи.items[i] в C# делает проверку, а в C++ нет.Получается мой тест оценивает цену этой проверки с точки зрения производительности, разве это не полезно?
Кстати, очень интересно, останется ли проверка в .Net Native?
Я хотел посмотреть на работу «не последовательного» доступа к элементам.
Давайте разделять задачи. Если мы измеряем доступ к элементу, то давайте измерять доступ к элементу. Если вы хотите посмотреть, насколько быстро можно последовательно обратиться к каждому из элементов массива, то посчитайте сумму элементов. Если вас интересует рандомизированный доступ к массиву, то тут скорее надо вести речь о промохах кэша, о размерах L1/L2/L3, о работе CPU. Постарайтесь поставить себе задачу максимально чётко и решайте именно её.
014B04D4 mov eax,dword ptr [ebp-3Ch] ;; [ebp-3Ch] = items
014B04D7 mov ebx,dword ptr [eax+4]
if(items[i]<items[j]) {
014B04DA mov eax,dword ptr [ebp-1Ch] ;; [ebp-1Ch] = i
014B04DD mov edx,dword ptr [ebp-3Ch]
014B04E0 cmp eax,ebx
014B04E2 jae 014B0574 ;; exception?
014B04E8 mov esi,dword ptr [edx+eax*4+8] ;; items[i]
014B04EC mov eax,dword ptr [ebp-3Ch]
014B04EF cmp ecx,ebx ;; ecx = j
014B04F1 jae 014B0574
014B04F7 mov edi,dword ptr [eax+ecx*4+8] ;; items[j]
014B04FB cmp esi,edi
014B04FD jge 014B0510
014B04FF mov eax,dword ptr [ebp-1Ch]
if(items[i]<items[j]) {
014B0502 mov edx,dword ptr [ebp-3Ch]
014B0505 mov dword ptr [edx+eax*4+8],edi ;; items[i]= saved items[j]
items[j]=tmp;
014B0509 mov eax,dword ptr [ebp-3Ch]
014B050C mov dword ptr [eax+ecx*4+8],esi ;; items[j] = saved items[i]
if(I[i]<I[j]) {
015204DF mov edx,dword ptr [ebp-18h] ;; [ebp-18h] = I
015204E2 mov eax,dword ptr [edx+ebx*4] ;; ebx = i
015204E5 mov edi,dword ptr [ebp-18h]
015204E8 cmp eax,dword ptr [edi+esi*4] ;; esi = j
015204EB jge 0152050B
tmp=I[i];
015204ED mov edx,dword ptr [ebp-18h]
015204F0 mov eax,dword ptr [edx+ebx*4]
015204F3 mov dword ptr [ebp-20h],eax
I[i]=I[j];
015204F6 mov ecx,dword ptr [ebp-18h]
015204F9 mov edi,dword ptr [ebp-18h]
I[i]=I[j];
015204FC mov eax,dword ptr [edi+esi*4]
015204FF mov dword ptr [ecx+ebx*4],eax
I[j]=tmp;
01520502 mov edi,dword ptr [ebp-18h]
01520505 mov eax,dword ptr [ebp-20h]
01520508 mov dword ptr [edi+esi*4],eax
int a=I[i],b=I[j];
if(a<b) {
I[i]=b;
I[j]=a;
}
int a=I[i],b=I[j];
021D04DF mov edx,dword ptr [ebp-18h]
021D04E2 mov ecx,dword ptr [edx+edi*4]
021D04E5 mov edx,dword ptr [ebp-18h]
021D04E8 mov edx,dword ptr [edx+esi*4]
if(a<b) {
021D04EB cmp ecx,edx
021D04ED jge 021D04FB
I[i]=b;
021D04EF mov ebx,dword ptr [ebp-18h]
021D04F2 mov dword ptr [ebx+edi*4],edx
I[j]=a;
021D04F5 mov edx,dword ptr [ebp-18h]
021D04F8 mov dword ptr [edx+esi*4],ecx
Интересно, почему он каждый раз достаёт I.
01322E1B mov eax,dword ptr [ebp-14h]
01322E1E cmp ebx,eax
01322E20 jae 01322E46
fixed(int* I=items) {
for(int k=0;k<100;k++) {
int* A=I;
for(int i=0;i<N;i++) A[i]=i;
int tmp;
for(int i=0;i<N;i++) {
for(int j=i;j<N;j++) {
if(A[i]<A[j]) {
tmp=A[i];
A[i]=A[j];
A[j]=tmp;
}
}
}
}
}
if(A[i]<A[j]) {
010404DF mov edi,dword ptr [edx+ecx*4]
010404E2 mov esi,dword ptr [edx+ebx*4]
010404E5 cmp edi,esi
010404E7 jge 010404EF
A[i]=A[j];
010404E9 mov dword ptr [edx+ecx*4],esi
A[j]=tmp;
010404EC mov dword ptr [edx+ebx*4],edi
int* E=I+N;
for(int* p=I;p!=E;p++) {
for(int* q=p;q!=E;q++) {
if(*p<*q) {
tmp=*p;
*p=*q;
*q=tmp;
}
}
}
if(*p<*q) {
00C104EC mov edx,dword ptr [edi]
00C104EE mov eax,dword ptr [esi]
00C104F0 cmp edx,eax
00C104F2 jge 00C104F8
*p=*q;
00C104F4 mov dword ptr [edi],eax
*q=tmp;
00C104F6 mov dword ptr [esi],edx
for(int* q=p;q!=E;q++) {
// Проверка границ отсутствует
for (int k = 0; k < array.Length - 1; ++k) {
array[k] = (uint)k;
}
// Проверка границ отсутствует
for (int к = 7; к < array.Length; ++к) {
array[k] = (uint)k;
}
// Проверка границ отсутствует
// JIT-компилятор удалит -1 из проверки границ и начнет со второго элемента
for (int k = 0; k < array.Length - 1; ++k) {
array[k + 1] = (uint)k;
}
// Проверка границ выполняется
for (int k = 0; k < array.Length / 2; ++k) {
array[k * 2J = (uint)k;
}
// Проверка границ выполняется
staticArray = array; // "staticArray" - это статическое поле вмещающего класса
for (int k = 0; k < staticArray.Length; ++k) (
staticArray[k] = (uint)k;
}
Но я думаю, что вы согласитесь, что даже самые сложные решения состоят из максимально простых элементов.
Сложное состоит из простого, по крайней мере в разработке ПО.
Если нет, то в чем я не прав, экстраполируя факт наличия расходов на менеджмент простого кода на более сложные случаи?

Заполнение двумерного массива 10000x100000 вложенными циклами.Или вы вообще не в курсе что такое cache miss? Ну и зачем тогда писать статью о «производительности» если даже базовые вещи надо разжевывать?
Вопрос итератор какого из циклов за какой индекс массива будет отвечать и наличия другого кода.А что, есть варианты?
cache miss, по идее должен возникать когда будет обращение к элементам массива находящимся друг от друга на расстоянии больше, чем размер кэша.Что и подтверждает мои слова о том что вы не в курсе как работает кэш. Почитайте хотябы вики на досуге, кэш работает по линиям. Нет, ну конечно если «больше чем размер кэша» — тоже miss будет, но условие совершенно не обязательное.
И другой вопрос в том как именно данный пример сравнивает С++ и C#?А вы выше читайте что вам сказали, а то уже забыли на что отвечали.
Кто-то из них способен избежать эффекта cache miss?
Как по мне, если брать среднестатистического программиста, то C# выигрывает в плане быстроты и удобства разработки, что вполне окупает потерю производительности.
uint i = 0; j = 0;
foreach (int itemI in items)
{
foreach(int imemJ in items)
{
if (itemI < itemJ)
{
items[j] = itemI;
items[i] = itemJ
}
++j;
}
++i;
}
Объявление int tmp; за циклом в случае C# экономит времяНе уж то на стеке даже примитивы размещать не умеет?
пытаюсь оценить стоимость выбора между С# и С++ с точки зрения производительности.
Стоимость оценивается исключительно в единицах времени, которое занимает код в runtime.
Для этого возьмем код сортировки из самых быстрых примеров и посмотрим во что он компилируется, смотреть будем используя отладчик Visual Studio 2010 и режим disassembly, в результате для сортировки увидим следующий код:
А вы пробовали вместо 10000 использовать items.Length?Я попробовал, ничего не поменялось в этом случае
Я привел disassembly релизного кода, а не дебажного.Как вы получили disassembly релизного кода? Распишите по шагам, пожалуйста. В .NET тут есть нюанс.
запустил отладку,На этом шаге все JIT-оптимизации отключились.
Console.ReadKey();ReadKey(), прицепитесь к процессу.Из тех, что попадались мне, самой содержательной показалась Head-to-head benchmark: C++ vs .NET
Собрал тесты под VS2015 и запустил.
какие именно инструкции кэшируются в начале цикла С#Кэшируется всё тело метода.
Но тут тесты С++ и С# находятся в равном положении — такой прогрев и там и там отсутствует
причем на столько что при загрузке данными ентерпрайз уровня«Данные энтерпрайз уровня» вот любят же люди термины выдумывать. Нет никаких «данных энтерпрайз уровня», в enterprise может встречаться хоть 10 строк хоть 10 миллионов, границы нету.
Кстати WPF достаточно спорная по производительности библиотека, например в статьеПриводя в пример один стандартный контрол (у которого есть несколько сторонних альтернатив к тому же). Очень ценный совет, и очень ценное обобщение конечно же.
хотя и пришло в русский язык из других языков:
static int Unpack(int[] rec,int len,int[] res) {
int p=0,np=0;
int a=rec[p++],c=31;
while(p<len) {
for(int i=0;i<NCh;i++) {
int t=a&1;
int x=(t==0) ? LShort[i]+1 : 17;
int r=a;
if(x<c) {
a>>=x; c-=x;
} else {
int s=x-c;
a=rec[p++];
r|=a<<c;
a>>=s;
c=31-s;
}
x=32-x; r=(r<<x)>>(x+1);
if(t==0) r+=Val[i];
Val[i]=r;
res[np++]=UCvt[r&65535];
}
}
return np;
}
void TestArray() {
for(int i=0;i<L;i++) Arr1[i]=i;
for(int i=0;i<Arr1.Length;i++) {
for(int j=i+1;j<Arr1.Length;j++) {
if(Arr1[i]<Arr1[j]) {
int t=Arr1[i];
Arr1[i]=Arr1[j];
Arr1[j]=t;
}
}
}
}
void TestArray2() {
for(int i=0;i<Arr1.Length;i++) Arr1[i]=i;
for(int i=0;i<Arr1.Length;i++) {
for(int j=i+1;j<Arr1.Length;j++) {
int a=Arr1[i],b=Arr1[j];
if(a<b) {
Arr1[i]=b;
Arr1[j]=a;
}
}
}
}
void TestArray3() {
for(int i=0;i<L;i++) Arr1[i]=i;
for(int i=0;i<L;i++) {
for(int j=i+1;j<L;j++) {
int a=Arr1[i],b=Arr1[j];
if(a<b) {
Arr1[i]=b;
Arr1[j]=a;
}
}
}
}
unsafe void TestFixed1() {
fixed(int* A=Arr1) {
int* arr1=A;
for(int i=0;i<L;i++) arr1[i]=i;
for(int i=0;i<L;i++) {
for(int j=i+1;j<L;j++) {
int a=arr1[i],b=arr1[j];
if(a<b) {
arr1[i]=b;
arr1[j]=a;
}
}
}
}
}
unsafe void TestFixed2() {
fixed(int* A=Arr1) {
for(int i=0;i<L;i++) A[i]=i;
int* end=A+L;
for(int *p=A;p<end;p++) {
for(int* q=p+1;q<end;q++) {
int a=*p,b=*q;
if(a<b) {
*p=b;
*q=a;
}
}
}
}
}
Test Intel ARM ARM --optimize=unsafe
Static class field:
Array, no vars 17.31 33.36 19.95
Array, vars, Length 12.02 21.01 14.74
Array, vars, L 12.01 19.04 14.32
Pointer, index 5.76 6.37 6.79
Pointer, move 3.67 3.02 3.02
Class field:
Array, no vars 8.57 30.73 17.28
Array, vars, Length 8.56 23.25 13.92
Array, vars, L 8.62 18.66 12.24
Pointer, index 5.74 6.42 5.96
Pointer, move 3.72 3.31 3.97
Local variable:
Array, no vars 5.75 22.17 9.82
Array, vars, Length 5.77 17.24 7.66
Array, vars, L 6.08 17.05 7.64
Pointer, index 5.75 5.17 6.39
Pointer, move 3.68 3.04 3.44
Parameter:
Array, no vars 5.75 25.12 9.47
Array, vars, Length 5.79 17.21 7.65
Array, vars, L 6.11 16.98 7.62
Pointer, index 6.63 8.04 6.67
Pointer, move 3.70 4.27 3.96
Copy of static field:
Array, no vars 5.73 24.86 9.48
Array, vars, Length 5.78 17.20 7.64
Array, vars, L 6.07 16.98 7.90
Pointer, index 5.75 6.79 6.31
Pointer, move 3.66 3.02 3.95
Сравнение производительности С++ и C#