Если оператор True Block выполняется, когда условие ложно - PullRequest
10 голосов
/ 20 января 2012

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

[StructLayout(LayoutKind.Explicit)]
struct Converter
{
    [FieldOffset(0)]
    public Byte[] Byte;

    [FieldOffset(0)]
    public UInt64[] UInt64;
}

/// <summary>
/// Compares two streams for byte-by-byte equality.
/// </summary>
/// <param name="target">The target stream.</param>
/// <param name="compareTo">The stream to compare the target to.</param>
/// <returns>A value indicating whether the two streams are identical.</returns>
public static bool CompareBytes(this Stream target, Stream compareTo)
{
    if (target == null && compareTo == null)
        return true;
    if (target == null || compareTo == null)
        return false;
    if (target.Length != compareTo.Length)
        return false;
    if (object.ReferenceEquals(target, compareTo))
        return true;
    if (!target.CanRead || !target.CanSeek)
        throw new ArgumentOutOfRangeException("target");
    if (!compareTo.CanRead || !compareTo.CanSeek)
        throw new ArgumentOutOfRangeException("target");
    lock (target)
    {
        lock (compareTo)
        {
            var origa = target.Position;
            var origb = compareTo.Position;
            try
            {
                target.Position = compareTo.Position = 0;

                // Shrink the number of comparisons.
                var arr1 = new byte[4096];
                var convert1 = new Converter() { Byte = arr1 };
                var arr2 = new byte[4096];
                var convert2 = new Converter() { Byte = arr2 };

                int len;
                while ((len = target.Read(arr1, 0, 4096)) != 0)
                {
                    if (compareTo.Read(arr2, 0, 4096) != len)
                        return false;
                    for (var i = 0; i < (len / 8) + 1; i++)
                        if (convert1.UInt64[i] != convert2.UInt64[i])
                            return false;
                }

                return true;
            }
            finally
            {
                target.Position = origa;
                compareTo.Position = origb;
            }
        }
    }
}

Проблема в том, что блок convert1.UInt64[i] != convert2.UInt64[i] if (возвращающий false) оценивается, даже когда значения равны.Я проверил каждого по отдельности, а затем проверил результат «не равных». Я в полном недоумении :

Values are not equal

Я не перепутал указатель инструкций - это то, как исполняется код и активен пин-код.1014 *

Есть идеи, как это могло произойти?

Ответы [ 2 ]

11 голосов
/ 20 января 2012
  for (var i = 0; i < (len / 8) + 1; i++)

Отладчику в целом сложно с этим объединением, он не может отображать содержимое массива, когда я его пробую.Но основной проблемой, без сомнения, является +1 в выражении for () end.Это индексирует массив после его последнего элемента, когда len делится на 8. Среда выполнения не может отловить эту ошибку, перекрытие массивов приводит к тому, что свойство Length имеет поддельное значение.Далее происходит неопределенное поведение, вы читаете байты, которые не являются частью массива.Обходной путь - сделать массив на 7 байт длиннее.

Этот код не совсем оптимизация, чтение и сравнение uint64 на 32-битной машине стоит дорого, особенно если массив не выровнен правильно.Около 50% шансов на это.Лучшей мышеловкой является использование функции C memcmp () среды выполнения C, доступной на любом компьютере с Windows:

    [DllImport("msvcrt.dll")]
    private static extern int memcmp(byte[] arr1, byte[] arr2, int cnt);

И используйте ее следующим образом:

    int len;
    while ((len = target.Read(arr1, 0, 4096)) != 0) {
        if (compareTo.Read(arr2, 0, 4096) != len) return false;
        if (memcmp(arr1, arr2, len) != 0) return false;
    }
    return true;

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

1 голос
/ 20 января 2012

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

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

...