18 May 2010

Высокоуровневая абстракция в программировании. Наш друг и враг

Lumber room
Большая часть прогресса, который мы наблюдаем за окном, это результат человеческой лени. Лень было красить забор кисточкой, изобрели валик. Лень было красить валиком, изобрели пульверизатор. Ну Вы поняли. Какое это все имеет отношение к программированию? Самое непосредственное!

Друг


Первые программисты манипулировали такими понятиями как «0» и «1» и это было долго и непонятно. Человеческая лень предложила при помощи ноликов и единичек написать программу преобразующую MOV, ADD, JMP в новые нолики и единички, понятные процессору.

В дальнейшем лень не дремала и призывала вместо 6-10 строк ассемблера писать 1 команду высокого уровня. Затем появились еще более ленивые вещи. ООП, базы данных и так далее, и так далее. Программы становились все понятнее и понятнее, поиск ошибок сокращался в разы, благодаря различным фреймверкам увеличивалась скорость разработки. Высокоуровневая абстракция шествовала по планете. И все были счастливы?

Враг


Но враг не дремал! В один прекрасный момент программист сталкивается с проблемой, которую на данном уровне абстракции нельзя ни то что решить, но даже понять откуда у этой проблемы растут ноги. Для того, чтобы понять что происходит приходится лезть в дебри ноликов и единичек. Примеры? Да пожалуйста (все примеры даны на языке C#).

Пример 1

static void Main(string[] args)
{
double tenth = 0.1;
double first = 0;
for (int i = 0; i < 10; i++)
{
first += tenth;
}

double eighth = 0.125;
double second = 0;
for (int i = 0; i < 8; i++)
{
second += eighth;
}
Console.WriteLine(first == second);
Console.WriteLine("0.1 * 10 = {0}\n0.125 * 8 = {1}", first, second);

Console.ReadKey();
}


Попробуйте предположить, что мы увидим на экране.

Попробовали? И что у вас получилось? Так вот, для тех кто предположил, ну и для тех кто поленился и просто дочитал до этого места, я информирую, мы увидим — False.
image
Обратили внимание, что значения совпадают? В принципе если заменить 10 на 100, а 8 на 80, то мы увидим, что значения отличаются, но здесь то О_о
Вот он уровень нулей и единиц. В принципе, понимая как представляются числа с плавающей точкой, понятно, что 1/8 это ни что иное как 2 в -3 степени и складываться она будет без погрешностей, а вот 0.1 представить в виде суммы целых степеней двойки в разрядности современного double не получается. Вот здесь и набегает погрешность.
Продолжим?

Пример 2

static void Main(string[] args)
{
int n = 10000;
int[,] array1 = new int[n, n];
int[,] array2 = new int[n, n];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
array1[i, j] = 0;
array2[i, j] = 0;
}
}
DateTime begin = DateTime.Now;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
array1[j, i]++;
}
}
Console.WriteLine("Время работы [j, i] = {0}", DateTime.Now.Subtract(begin).TotalSeconds);

begin = DateTime.Now;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
array1[i, j]++;
}
}
Console.WriteLine("Время работы [i, j] = {0}", DateTime.Now.Subtract(begin).TotalSeconds);

Console.ReadKey();
}

Самое удивительное, что изменив порядок индексов при доступе к элементам массива мы получаем изменение скорости работы почти в два раза (на моем компьютере первый цикл отработал за 2,16 с; второй за 1,13 с). При увеличении n до 11000, разница составляет уже 5 раз.
От куда такая разница? Опять же с уровня нулей и единиц. В данном случае проявляется работа с кэшем и то, что двухмерный массив в памяти на самом деле одномерен.

Пример 3

С появлением generic типов этот пример может и не актуален, но все таки не удержусь.
class MyPack
{
public int Value { get; set; }
}

static void Main(string[] args)
{
int n = 100000;
object[] array1 = new object[n];
object[] array2 = new object[n];
for (int i = 0; i < n; i++)
{
array1[i] = 0;
array2[i] = new MyPack() { Value = 0 };
}
DateTime begin = DateTime.Now;
for (int i = 0; i < n; i++)
{
array1[i] = (int)array1[i] + 1;
}
Console.WriteLine("Время работы c int = {0}", DateTime.Now.Subtract(begin).TotalSeconds);

begin = DateTime.Now;
for (int i = 0; i < n; i++)
{
((MyPack)array2[i]).Value = ((MyPack)array2[i]).Value + 1;
}
Console.WriteLine("Время работы с MyPack = {0}", DateTime.Now.Subtract(begin).TotalSeconds);
Console.ReadKey();
}


В первом случае время выполнения составило 0,017 секунды, во втором 0,005 секунды. Здесь вступает в силу не на столько низкоуровневые вещи, а особенности работы с преобразованием простых типов к типу object (кто заинтересуется можно почитать про упаковку и распаковку).

Выводы


Начну с того, что я не считаю высокоуровневую абстракцию злом. И название статьи, если вы обратили внимание, именно о том, что высокоуровневая абстракция нам друг, и чтобы она не не превратилась во врага, пойду ка я почитаю про новые архитектуры процессоров Intel.

P.s. Во время написания статьи выходил в коридор, где столкнулся с коллегой. Так вот, он подошел с проблемой, что при разработке приложения для Windows Mobile приложение адекватно работает до тех пор, пока КПК не уходит в sleep. При выводе его в нормальный режим приложения в памяти уже нет, сообщений об ошибках нет, ничего нет. Вы как думаете по чему? Я думаю, что из-за дыры в высокоуровневой абстракции.
Tags:высокоуровневые языкиабстракция
Hubs: Lumber room
+9
500 2
Comments 21
Senior Android Developer
from 3,000 €Pure AppRemote job
Node.js developer (infrastructure)
from 100,000 to 300,000 ₽ЯндексМосква
Middle Android Developer (Клиентский сервис)
from 90,000 ₽KazanExpressИннополис
Senior Android Developer
from 200,000 ₽FunCorpМосква
Top of the last 24 hours