2014-01-13 5 views
3

У меня есть массив, и я хочу использовать одно значение несколько раз в том же методе.C# производительность с использованием индекса массива

int[] array = new array[] {1, 2, 3}; 

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

int x = array[1] * 2; 
int y = array[1] * 3; 
int z = array[1] * 4; 

или лучше создать локальную переменную ?

int value = array[1]; 
int x = value * 2; 
int y = value * 3; 
int z = value * 4; 

Я знаю, что его легче читать с помощью локальной переменной, но это меня просто заинтересовало, если оно делает какие-либо различия в производительности. ;-)

+6

Я бы выбрал второй подход не только для производительности, но и для избежания дублирования. –

+8

Micro-optimizations are evil – Steve

+0

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

ответ

2

Хотя я согласен, что такие микро-оптимизации являются злым и ненужным, и читаемости может быть более важным, это было довольно весело, чтобы сделать фиктивный ориентир для двух методов:

private static int TestWithIndex(int[] array) 
{ 
    int x = array[1] * 2; 
    int y = array[1] * 3; 
    int z = array[1] * 4; 
    return x + y + z; 
} 

private static int TestWithTemp(int[] array) 
{ 
    int value = array[1]; 
    int x = value * 2; 
    int y = value * 3; 
    int z = value * 4; 
    return x + y + z; 
} 

вызова Them int.MaxValue раз в режиме Release производит:

  • 12032 мс - для TestWithIndex
  • 10525 мс - для TestWithTemp

А потом давайте посмотрим на IL генерируемой (режим Release, Оптимизации включен):

TestWithIndex

.method private hidebysig static 
    int32 TestWithIndex (
     int32[] 'array' 
    ) cil managed 
{ 
    // Method begins at RVA 0x2564 
    // Code size 29 (0x1d) 
    .maxstack 2 
    .locals init (
     [0] int32 x, 
     [1] int32 y, 
     [2] int32 z, 
     [3] int32 CS$1$0000 
    ) 

    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldc.i4.1 
    IL_0003: ldelem.i4 
    IL_0004: ldc.i4.2 
    IL_0005: mul 
    IL_0006: stloc.0 
    IL_0007: ldarg.0 
    IL_0008: ldc.i4.1 
    IL_0009: ldelem.i4 
    IL_000a: ldc.i4.3 
    IL_000b: mul 
    IL_000c: stloc.1 
    IL_000d: ldarg.0 
    IL_000e: ldc.i4.1 
    IL_000f: ldelem.i4 
    IL_0010: ldc.i4.4 
    IL_0011: mul 
    IL_0012: stloc.2 
    IL_0013: ldloc.0 
    IL_0014: ldloc.1 
    IL_0015: add 
    IL_0016: ldloc.2 
    IL_0017: add 
    IL_0018: stloc.3 
    IL_0019: br.s IL_001b 

    IL_001b: ldloc.3 
    IL_001c: ret 
} // end of method Program::TestWithIndex 

Здесь мы видим три ldelem.i4 ,

TestWithTemp

.method private hidebysig static 
    int32 TestWithTemp (
     int32[] 'array' 
    ) cil managed 
{ 
    // Method begins at RVA 0x2590 
    // Code size 29 (0x1d) 
    .maxstack 2 
    .locals init (
     [0] int32 'value', 
     [1] int32 x, 
     [2] int32 y, 
     [3] int32 z, 
     [4] int32 CS$1$0000 
    ) 

    IL_0000: nop 
    IL_0001: ldarg.0 
    IL_0002: ldc.i4.1 
    IL_0003: ldelem.i4 
    IL_0004: stloc.0 
    IL_0005: ldloc.0 
    IL_0006: ldc.i4.2 
    IL_0007: mul 
    IL_0008: stloc.1 
    IL_0009: ldloc.0 
    IL_000a: ldc.i4.3 
    IL_000b: mul 
    IL_000c: stloc.2 
    IL_000d: ldloc.0 
    IL_000e: ldc.i4.4 
    IL_000f: mul 
    IL_0010: stloc.3 
    IL_0011: ldloc.1 
    IL_0012: ldloc.2 
    IL_0013: add 
    IL_0014: ldloc.3 
    IL_0015: add 
    IL_0016: stloc.s CS$1$0000 
    IL_0018: br.s IL_001a 

    IL_001a: ldloc.s CS$1$0000 
    IL_001c: ret 
} // end of method Program::TestWithTemp 

Вот только один ldelem.i4 конечно.

+0

Благодарим вас за быстрый ответ! Я предполагал, что это не будет иметь большого эффекта, но это меня просто заинтересовало. :-) –

2

Нет, не было бы разницы в производительности. Для этой работы:

int x = array[1] * 2; 

значение в array[1] собирается должны быть перемещены в ячейку памяти, в любом случае, когда IL генерируется. Затем оставшиеся операции будут оптимизированы компилятором (т. Е. Он не будет получать значение более одного раза).


Хорошо, чтобы уладить спор, я решил сбросить каждый, вот первый:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    IL_0000: nop 
    IL_0001: ldc.i4.3 
    IL_0002: newarr  [mscorlib]System.Int32 
    IL_0007: dup 
    IL_0008: ldtoken field valuetype '<PrivateImplementationDetails>{79A4FD92-FA37-4EB9-8056-B52A57262FBB}'/'__StaticArrayInitTypeSize=12' '<PrivateImplementationDetails>{79A4FD92-FA37-4EB9-8056-B52A57262FBB}'::'$$method0x6000001-1' 
    IL_000d: call  void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, 
                             valuetype [mscorlib]System.RuntimeFieldHandle) 
    IL_0012: stloc.0 
    IL_0013: ldloc.0 
    IL_0014: ldc.i4.1 
    IL_0015: ldelem.i4 
    IL_0016: stloc.1 
    IL_0017: ldloc.1 
    IL_0018: ldc.i4.2 
    IL_0019: mul 
    IL_001a: stloc.2 
    IL_001b: ldloc.1 
    IL_001c: ldc.i4.3 
    IL_001d: mul 
    IL_001e: stloc.3 
    IL_001f: ldloc.1 
    IL_0020: ldc.i4.4 
    IL_0021: mul 
    IL_0022: stloc.s z 
    IL_0024: ret 
} // end of method Program::Main 

и вот второй:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    IL_0000: nop 
    IL_0001: ldc.i4.3 
    IL_0002: newarr  [mscorlib]System.Int32 
    IL_0007: dup 
    IL_0008: ldtoken field valuetype '<PrivateImplementationDetails>{79A4FD92-FA37-4EB9-8056-B52A57262FBB}'/'__StaticArrayInitTypeSize=12' '<PrivateImplementationDetails>{79A4FD92-FA37-4EB9-8056-B52A57262FBB}'::'$$method0x6000001-1' 
    IL_000d: call  void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, 
                             valuetype [mscorlib]System.RuntimeFieldHandle) 
    IL_0012: stloc.0 
    IL_0013: ldloc.0 
    IL_0014: ldc.i4.1 
    IL_0015: ldelem.i4 
    IL_0016: stloc.1 
    IL_0017: ldloc.1 
    IL_0018: ldc.i4.2 
    IL_0019: mul 
    IL_001a: stloc.2 
    IL_001b: ldloc.1 
    IL_001c: ldc.i4.3 
    IL_001d: mul 
    IL_001e: stloc.3 
    IL_001f: ldloc.1 
    IL_0020: ldc.i4.4 
    IL_0021: mul 
    IL_0022: stloc.s z 
    IL_0024: ret 
} // end of method Program::Main 

Они точно кампанией с таким же - заявил я.

+7

'(т. Е. Он не будет получать значение более одного раза)' Как вы это знаете? Он не знает, что значение массива не будет изменено во время его выполнения, поэтому ему потребуется снова получить к нему доступ. – Servy

+0

@Servy, с этими тремя строками в строке, он определенно знает, что значение массива не может измениться - оно не устанавливается. –

+4

другой поток мог их менять. Это также означает, что вы предполагаете, что компилятор проводит анализ, чтобы попытаться определить, установлено ли это конкретное значение между обращениями. Это * очень * нетривиальная вещь. Что делать, если вызывается другой метод за пределами текущей области, что если событие запущено, у которого может быть обработчик, который мутирует массив и т. Д. Слишком много возможностей для реальной проверки. – Servy

1

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

int x = array[1] * 2; 
int y = array[1] * 3; 
int z = array[1] * 4; 

У этого есть несколько вопросов. Во-первых - чем больше у вас дублированный код, тем больше кода вы должны поддерживать и тем выше вероятность того, что в какой-то момент вы не будете модифицировать одну из кодовых копий. Второе - дублирование всегда означает, что у вас есть скрытые знания в вашем коде. Если какой-то код повторяется, то он имеет конкретное значение, которое вы не сделали очевидным. Например. у вас есть значение скорости во втором элементе массива. Сделать это знание явным:

int speed = array[1]; 
int x = speed * 2; // of course, magic numbers also should be replaced 
int y = speed * 3; 
int z = speed * 4; 

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

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