OberonCore
https://forum.oberoncore.ru/

Быстр-е/оптимизация поиска в матрице
https://forum.oberoncore.ru/viewtopic.php?f=2&t=2357
Страница 1 из 2

Автор:  Александр Ильин [ Вторник, 16 Февраль, 2010 00:11 ]
Заголовок сообщения:  Быстр-е/оптимизация поиска в матрице

Выделено: viewtopic.php?p=42858#p42858

Интереса ради сравнил скорость выполнения трёх написанных мною примеров. Естественно, единица измерения - секунды, делённые на сферических попугаев в вакууме (сек/спв).

Взята матрица arr: ARRAY 1000, 1000 OF INTEGER (16-битные слова в XDS). Во все элементы помещены значения 999, и только последний элемент arr[999, 999] := 1000. Соответственно, сделан поиск значения 1000 тремя вышеприведенными программами по 1000 раз (взято минимальное значение времени из 11 запусков подряд).

Результаты замеров (чем меньше, тем лучше):
- один WHILE: 13.635 сек/спв;
- два вложенных WHILE: 6.804 сек/спв;
- с локальной процедурой: 2.422 сек/спв. WIN!

Потом подумал: "Ой, чего это я в отладочном режиме проверяю?!" Включил всю оптимизацию, отключил все рантайм-проверки и запустил снова.
Результаты (чем меньше, тем лучше):
- один WHILE: 2.376 сек/спв;
- два вложенных WHILE: 1.279 сек/спв;
- с локальной процедурой: 0.813 сек/спв; WIN!

Прирост производительности за счёт оптимизации составил (чем меньше, тем лучше):
- один WHILE: 13.635/2.376=5.7386;
- два вложенных WHILE: 6.804/1.279=5.3198;
- с локальной процедурой: 2.422/0.813=2.9791. WIN!

Получается, что поиск с использованием локальной процедуры выигрывает по всем статьям. В случае с включенной оптимизацией он в полтора раза быстрее двух вложенных WHILE (из-за вынужденных лишних IF-ов внутри, которые тормозят работу), и в три раза быстрее единственного WHILE. В случае с отключенной оптимизацией, то есть, когда всякие проверки индексов выполняются в полном объёме, он почти в три раза быстрее двух WHILE и в пять с половиной раз быстрее одного WHILE. При этом состоит из простейших компонентов - паттерновых линейных поисков - и увеличивает читабельность, давая процедурное имя внутреннему циклу. Как говорится, EPIC WIN.

Два вложенных WHILE вдвое быстрее одного WHILE, что тоже интересно. : )

Если кому-то хочется, могу выложить исходники проекта на XDS.

Автор:  Alexey_Donskoy [ Вторник, 16 Февраль, 2010 09:05 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Александр Ильин писал(а):
Получается, что поиск с использованием локальной процедуры выигрывает по всем статьям. В случае с включенной оптимизацией он в полтора раза быстрее двух вложенных WHILE (из-за вынужденных лишних IF-ов внутри, которые тормозят работу), и в три раза быстрее единственного WHILE. В случае с отключенной оптимизацией, то есть, когда всякие проверки индексов выполняются в полном объёме, он почти в три раза быстрее двух WHILE и в пять с половиной раз быстрее одного WHILE.
Тестирование некорректно, как и анализ результатов.

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

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

Вывод: применённый хак имеет мало отношения к алгоритмизации и программированию, а чисто основан на использовании фич конкретного компилятора конкретного языка.

Автор:  Сергей Губанов [ Вторник, 16 Февраль, 2010 12:02 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Александр Ильин писал(а):
Получается, что поиск с использованием локальной процедуры выигрывает по всем статьям.
Я тоже проверил в ББ. Сравнивал вариант со вложенной процедурой с симуляцией цикла Дейкстры с помощью LOOP.

При размере матрицы 1000*1000 вложенная процедура победила с отрывом в 28%,
но при размере матрицы 100*100 победил цикл Дейкстры с отрывом в 53%
(для матрицы 32*32 цикл Дейкстры победил с отрывом уже в 92%).

В общем, для больших матриц рулит вложенная процедура, для малых - симуляция цикла Дейкстры с помощью LOOP.

Интересно было бы ещё проверить в Oberon-07 в котором цикл Дейкстры "родной". Там, по идее, если компилятор не сглупит, ЦД должен бы побеждать и на больших матрицах тоже.

Код:
MODULE TestSearch2D;

   IMPORT Log := StdLog, Services;

   CONST N = 1000;
      cpuClock = 2.21; (* частота моего процессора в гигагерцах *)

   VAR
      a: ARRAY 1000, 1000 OF INTEGER;
      i, j, v, n: INTEGER;
      t: LONGINT;
      dt: REAL;

   PROCEDURE FindInLine (IN a: ARRAY OF INTEGER; OUT pos: INTEGER): BOOLEAN;
      VAR i: INTEGER;
   BEGIN i := 0;
      WHILE (i < LEN(a)) & ~(a[i] = v) DO INC(i) END;
      pos := i;
      RETURN (i < LEN(a))
   END FindInLine;

BEGIN
   FOR i := 0 TO LEN(a, 0) -1 DO
      FOR j := 0 TO LEN(a, 1) -1 DO
         a[i, j] := 999
      END
   END;
   v := 1000;
   a[LEN(a, 0)-1, LEN(a, 1)-1] := v;

   (************************************)

   t := Services.Ticks();
   FOR n := 1 TO N DO
      i := 0; j := 0;
      LOOP IF (j < LEN(a, 1)) & ~(a[i, j] = v) THEN
         INC(j)
      ELSIF (j = LEN(a, 1)) & (i < LEN(a, 0)) THEN
         j := 0; INC(i)
      ELSE EXIT END END;
   END;
   dt := Services.Ticks() - t;
   Log.String("Симуляция цикла Дейкстры:"); Log.Ln;
   Log.String("i = "); Log.Int(i); Log.Ln;
   Log.String("j = "); Log.Int(j); Log.Ln;
   Log.Real(dt); Log.String(" секунд на весь тест"); Log.Ln;
   Log.Real(dt / N * 1000); Log.String(" микросекунд на один поиск"); Log.Ln;
   Log.Real(dt / N * 1000000 * cpuClock / (LEN(a, 0)*LEN(a, 1))); Log.String(" тактов на элемент"); Log.Ln;

   (************************************)

   t := Services.Ticks();
   FOR n := 1 TO N DO
      i := 0; j := 0;
      WHILE (i < LEN(a, 0)) & ~FindInLine(a[i], j) DO INC(i) END
   END;
   dt := Services.Ticks() - t;
   Log.String("Использование процедуры:"); Log.Ln;
   Log.String("i = "); Log.Int(i); Log.Ln;
   Log.String("j = "); Log.Int(j); Log.Ln;
   Log.Real(dt); Log.String(" секунд на весь тест"); Log.Ln;
   Log.Real(dt / N * 1000); Log.String(" микросекунд на один поиск"); Log.Ln;
   Log.Real(dt / N * 1000000 * cpuClock / (LEN(a, 0)*LEN(a, 1))); Log.String(" тактов на элемент"); Log.Ln
END TestSearch2D.

Автор:  Alexey_Donskoy [ Вторник, 16 Февраль, 2010 12:09 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Друзья, ну некорректно же с такой процедурой сравнивать! viewtopic.php?p=42887#p42887

Перепишите тогда два цикла с такой же оптимизацией, как в случае процедуры!

Автор:  Info21 [ Вторник, 16 Февраль, 2010 12:21 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Сергей Губанов писал(а):
проверил в ББ. Сравнивал вариант со вложенной процедурой с симуляцией цикла Дейкстры с помощью LOOP.

При размере матрицы 1000*1000 вложенная процедура победила с отрывом в 28%,
но при размере матрицы 100*100 победил цикл Дейкстры с отрывом в 53%
(для матрицы 32*32 цикл Дейкстры победил с отрывом уже в 92%).

В общем, для больших матриц рулит вложенная процедура, для малых - симуляция цикла Дейкстры с помощью LOOP.

Интересно было бы ещё проверить в Oberon-07 в котором цикл Дейкстры "родной". Там, по идее, если компилятор не сглупит, ЦД должен бы побеждать и на больших матрицах тоже.
Не должен. Только граница, где процедура начинает обгонять, могла бы сдвинуться (см. ранее), и то вряд ли (цикл Дейкстры никак нельзя реализовать по особенному по сравнению с LOOP IF ELSIF ELSE EXIT END END).

Автор:  Виктор О [ Вторник, 16 Февраль, 2010 12:43 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Тогда до кучи алгоритм от партизана :D
Код:
VAR
   arr: ARRAY N, M OF INTEGER;
   i, k: INTEGER;
   find,found: BOOLEAN;
BEGIN
   i := 0;  k := 0;
   find := TRUE;
   WHILE find DO
       found := arr [i, k] = x;
       IF found THEN
          find:=FALSE;
       ELSEIF i<N-1 THEN
            INC (i)
       ELSEIF k<M-1 THEN
            INC (k); i:=0
       ELSE
            find:=FALSE
       END
   END
END

Автор:  Сергей Губанов [ Вторник, 16 Февраль, 2010 12:46 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Info21 писал(а):
Не должен. Только граница, где процедура начинает обгонять, могла бы сдвинуться (см. ранее), и то вряд ли (цикл Дейкстры никак нельзя реализовать по особенному по сравнению с LOOP IF ELSIF ELSE EXIT END END).
Вот ЦД:

WHILE (j < LEN(a, 1)) & ~(a[i, j] = v) DO INC(j)
ELSIF (j = LEN(a, 1)) & (i < LEN(a, 0)) DO j := 0; INC(i)
END

Обратим внимание на первую строчку

WHILE (j < LEN(a, 1)) & ~(a[i, j] = v) DO INC(j)

Если компилятор не сглупит, то он заменит её на такую:

WHILE (j < LEN(b)) & ~(b[j] = v) DO INC(j)

где "b = a[i]". И тогда ЦД будет выполняться не медленнее чем в случае со вложенной процедурой, так как именно этот цикл написан в её теле.

Автор:  Сергей Губанов [ Вторник, 16 Февраль, 2010 13:47 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Придумал ещё один вариант:
Код:
      i := -1;
      REPEAT
         j := 0; INC(i);
         IF i < LEN(a, 0) THEN
            WHILE (j < LEN(a, 1)) & ~(a[i, j] = v) DO INC(j) END;
         END
      UNTIL j < LEN(a, 1);

      IF i < LEN(a, 0) THEN нашли ELSE не нашли END

Он чуть-чуть обгоняет симуляцию ЦД на больших матрицах, но отстаёт на малых.

Компилятор упорно тупит не желая оптимизировать строку

WHILE (j < LEN(a, 1)) & ~(a[i, j] = v) DO INC(j) END;

до строки

WHILE (j < LEN(b)) & ~(b[j] = v) DO INC(j) END;

где "b = a[i]".

Автор:  Александр Ильин [ Вторник, 16 Февраль, 2010 13:56 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Сергей, раз уж вы этим занимаетесь, проверьте, пожалуйста, влияет ли на быстродействие замена LEN(a) на соответствующую константу (N либо M)?

Автор:  Peter Almazov [ Вторник, 16 Февраль, 2010 14:01 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

У Вас LEN(a) меняется внутри цикла что ли? Зачем ее каждый раз вычислять?
UPD: опоздал :)

Автор:  Info21 [ Вторник, 16 Февраль, 2010 16:04 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Peter Almazov писал(а):
У Вас LEN(a) меняется внутри цикла что ли? Зачем ее каждый раз вычислять?
А она не вычисляется, а берется.

Автор:  Info21 [ Вторник, 16 Февраль, 2010 16:18 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Сергей Губанов писал(а):
Вот ЦД:
Да, это тоже цикл Дейкстры. Только мой лучше :)

Автор:  Сергей Губанов [ Вторник, 16 Февраль, 2010 17:02 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Если тип массива объявлен с фиксированным размером (что тут и сделано), то LEN(a) - константа подставляемая во время компиляции.

Если тип массива объявлен открытым, то LEN(a) - переменная доступная только для чтения. Вводить вместо неё другую переменную (которая к тому же ещё и будет доступна для записи) смысла нет (или даже вредно).

Автор:  Сергей Губанов [ Вторник, 16 Февраль, 2010 17:06 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Info21 писал(а):
Только мой лучше :)
А мой последний вариант с REPEAT-ом ещё лучше :D

Автор:  Info21 [ Вторник, 16 Февраль, 2010 18:41 ]
Заголовок сообщения:  Re: Базовые паттерны циклов

Сергей Губанов писал(а):
Info21 писал(а):
Только мой лучше :)
А мой последний вариант с REPEAT-ом ещё лучше :D
хум хау.

Автор:  QWERTYProgrammer [ Среда, 17 Февраль, 2010 00:09 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Сергей Губанов писал(а):

Компилятор упорно тупит не желая оптимизировать строку

WHILE (j < LEN(a, 1)) & ~(a[i, j] = v) DO INC(j) END;

до строки

WHILE (j < LEN(b)) & ~(b[j] = v) DO INC(j) END;

где "b = a[i]".


Так вроде компилятор в ББ неоптимизирующий?

Автор:  Info21 [ Среда, 17 Февраль, 2010 07:30 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

QWERTYProgrammer писал(а):
Так вроде компилятор в ББ неоптимизирующий?
И слава богу!
(Свят, свят, свят!...)

Автор:  Сергей Губанов [ Среда, 17 Февраль, 2010 11:36 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

QWERTYProgrammer писал(а):
Так вроде компилятор в ББ неоптимизирующий?
Смотря что под этим понимать. Программы ББ работают удовлетворительно шустро. В то же самое время если, например, в MS VS C# снять галку "оптимизация", то тормоза будут ещё те.

Кстати, сравнивая с C# обнаружил следующее различие. Если в Обероне объявлена процедура

PROCEDURE f (VAR line: ARRAY OF INTEGER);

и объявлена переменная

VAR a: ARRAY 10, 10 OF INTEGER;

то в ту процедуру можно засунуть строку матрицы: f(a[2]). А в C# этого сделать нельзя!

void f (int[] x)

int[,] a = new int[10, 10];

f(a[2]); <-- низя!!!

То есть самый быстрый выриант с процедурой (в которой осуществлялся проход по строке) в C# нереализуем!!!

Автор:  Виктор О [ Среда, 17 Февраль, 2010 11:51 ]
Заголовок сообщения:  Re: Быстр-е/оптимизация поиска в матрице

А где же Свидетель Си с алгоритмом на низкоуровневых массивах - в одну строчку и пустым телом цикла?
Нехорошо-с, совсем распугали оппонентов :D

Автор:  Info21 [ Среда, 17 Февраль, 2010 12:28 ]
Заголовок сообщения:  Re: Поиск элемента матрицы

Сергей Губанов писал(а):
то в ту процедуру можно засунуть строку матрицы: f(a[2]). А в C# этого сделать нельзя!
Любопытно. Чем же они думали.

Эта вещь нередко встречается при работе с матрицами и т.п.
Естественная такая вещь.

Может быть, не вещь первого плана, но всё-таки показатель качества проектирования -- ортогональность конструкций.

Страница 1 из 2 Часовой пояс: UTC + 3 часа
Powered by phpBB® Forum Software © phpBB Group
https://www.phpbb.com/