powered by simpleCommunicator - 2.0.59     © 2025 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
25 сообщений из 62, страница 1 из 3
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057482
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Есть у меня тут задача, накидал вариант решения
Если коротко, условие задачи выглядит примерно так:
  • Должно быть хранилище указателей
  • Есть всего 3 операции: добавить, удалить и итератор
  • Возможны дубликаты (не часто), функция удаления должна возвращать, остался ли дубликат
  • Потребление оперативной памяти желательно свести к минимуму
  • Размер задействованной памяти должен быть кратен 16 байтам на 64 битах
Раньше я использовал только списки, там всё понятно. Здесь могут быть нюансы
В общем почитал статью А. Шарахова, исходники дельфийского словаря
И получилась примерно такая концепция:
- размер равен степени 2 минус 1 (минимальный размер 7)
- бакет это некоторое виртуальное место, куда можно положить элемент, количество бакетов равно размеру таблицы
- индекс это позиция, в которой хранится элемент
- бакет может быть меньше индекса, это значит, что было несколько элементов с одним бакетом
- если бакет сильно больше индекса, значит было переполнение в конце
- при удалении возвращается True если есть дубликат


Я зафигачил тестовый проект, где есть проверки: элементарные в случае Release и доскональные в случае Debug
Смущает производительность. Даже 100k элементов без дубликатов считаются 1,5 секунды в Release . С дубликатами - уже 6 секунд.
В общем есть ощущение, что я что-то делаю не так и производительность должна быть на порядок выше. Смотрите аттач

Немного кода под катом:
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
type
  PHashTable = ^THashTable;
  THashTable = record
    Capacity: Cardinal;
    Count: Cardinal;
    Items: array[0..0] of Pointer;
  end;

const
  HASH_SHIFT = 6;
  HASH_EMPTY = Pointer(High(NativeUInt));
  HASH_MINSIZE = 7;

function HashTableGrowThreshold(const ACapacity: NativeUInt): NativeUInt; inline;
var
  LPow2: NativeUInt;
begin
  LPow2 := (ACapacity + 1);
  Result := (LPow2 shr 2) + (LPow2 shr 1);
end;

function HashTableFixBucket(const ABucket: NativeUInt; const ACapacity: NativeUInt): NativeUInt; inline;
begin
  Result := ABucket;
  if (Result = ACapacity) then
    Result := 0;
end;

function HashTableBucketIncrement(const ABucket: NativeUInt; const ACapacity: NativeUInt): NativeUInt; inline;
begin
  Result := HashTableFixBucket(ABucket + 1, ACapacity);
end;

function HashTableBucket(const P: Pointer; const ACapacity: NativeUInt): NativeUInt; inline;
begin
  Result := HashTableFixBucket((NativeUInt(P) shr HASH_SHIFT) and ACapacity, ACapacity);
end;

function HashTableIndex(const AItems: PPointer; const ACapacity: NativeUInt; const P: Pointer;
  const AAddMode: Boolean): NativeUInt;
var
  LValue: Pointer;
  LSourceBucket, LPrevBucket, LBucket: NativeUInt;
begin
  Result := HashTableBucket(P, ACapacity);

  // наиболее благоприятный случай
  LValue := AItems[Result];
  if (LValue = HASH_EMPTY) or (LValue = P) then
    Exit;

  // пропускаем лишние большие бакеты если было переполнение
  // или находим свободное место
  LSourceBucket := Result;
  LBucket := HashTableBucket(LValue, ACapacity);
  if (LBucket > LSourceBucket) then
  repeat
    LPrevBucket := LBucket;

    Result := HashTableBucketIncrement(Result, ACapacity);
    LValue := AItems[Result];
    if (LValue = HASH_EMPTY) then
      Exit;
    LBucket := HashTableBucket(LValue, ACapacity);
  until (LBucket < LPrevBucket);

  // пропускаем лишние малые бакеты если было переполнение
  // или находим свободное место
  if (AAddMode) then
  begin
    repeat
      if (LBucket >= LSourceBucket) then
        Exit;

      Result := HashTableBucketIncrement(Result, ACapacity);
      LValue := AItems[Result];
      if (LValue = HASH_EMPTY) then
        Exit;
      LPrevBucket := LBucket;
      LBucket := HashTableBucket(LValue, ACapacity);
      if (LPrevBucket > LBucket) then
        Break;
    until (False);
  end else
  begin
    repeat
      if (LBucket > LSourceBucket) or (LValue = P) then
        Exit;

      Result := HashTableBucketIncrement(Result, ACapacity);
      LValue := AItems[Result];
      if (LValue = HASH_EMPTY) then
        Exit;
      LPrevBucket := LBucket;
      LBucket := HashTableBucket(LValue, ACapacity);
      if (LPrevBucket > LBucket) then
        Break;
    until (False);
  end;
end;

procedure HashTableInsert(const AItems: PPointer; const ACapacity, AIndex: NativeUInt; const P: Pointer);
var
  LIndex: NativeUInt;
  LValue, LOldValue: Pointer;
begin
  LIndex := AIndex;
  LValue := P;

  repeat
    LOldValue := AItems[LIndex];
    AItems[LIndex] := LValue;
    if (LOldValue = HASH_EMPTY) then
      Exit;

    LValue := LOldValue;
    LIndex := HashTableBucketIncrement(LIndex, ACapacity);
  until (False);
end;

procedure HashTableAdd(var AHashTable: PHashTable; const P: Pointer);
var
  LCapacity: NativeUInt;
begin
  LCapacity := AHashTable.Capacity;
  if (AHashTable.Count = HashTableGrowThreshold(LCapacity)) then
  begin
    LCapacity := (LCapacity + 1) shl 1 - 1;
    HashTableRehash(AHashTable, LCapacity);
  end;

  Inc(AHashTable.Count);
  HashTableInsert(@AHashTable.Items[0], LCapacity,
    HashTableIndex(@AHashTable.Items[0], LCapacity, P, True), P);
end;

function HashTableRemove(var AHashTable: PHashTable; const P: Pointer): Boolean;
var
  LItems: PPointer;
  LCapacity: NativeUInt;
  LIndex, LNextIndex: NativeUInt;
  LValue: Pointer;
begin
  LItems := @AHashTable.Items[0];
  LCapacity := AHashTable.Capacity;
  LIndex := HashTableIndex(LItems, LCapacity, P, False);
  if (LItems[LIndex] <> P) then
    Exit(False);

  Result := False;
  repeat
    LNextIndex := HashTableBucketIncrement(LIndex, LCapacity);
    LValue := LItems[LNextIndex];

    if (LValue = HASH_EMPTY) then
      Break;

    if (LValue = P) then
      Result := True;

    if (LNextIndex = HashTableBucket(LValue, LCapacity)) then
      Break;

    LItems[LIndex] := LValue;
    LIndex := LNextIndex;
  until (False);
  LItems[LIndex] := HASH_EMPTY;

  Dec(AHashTable.Count);
  if (LCapacity <> HASH_MINSIZE) then
  begin
    LCapacity := LCapacity shr 1;
    if (AHashTable.Count = HashTableGrowThreshold(LCapacity)) then
      HashTableRehash(AHashTable, LCapacity);
  end;
end;

...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057496
Aleksandr Sharahov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

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

P.S. Как будет время планирую еще ускорить для очень больших таблиц, но это не твой случай.
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057498
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Aleksandr Sharahov,

Во-первых, у тебя там юзается другая организация данных
Во-вторых, не рассчитана на дубликаты
В-третьих, у тебя там используются индексы как признак пустой ячейки. У меня могут быть «спец» указатели, совпадающие с индексом
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057506
Aleksandr Sharahov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU
Aleksandr Sharahov,

Во-первых, у тебя там юзается другая организация данных
Во-вторых, не рассчитана на дубликаты
В-третьих, у тебя там используются индексы как признак пустой ячейки. У меня могут быть «спец» указатели, совпадающие с индексом


1. Ну да, другая, более быстрая, тебе же это надо?
2. Рассчитана, но не по-твоему. А ты правда не знаешь, как сделать их различимыми или как хранить цепочку дубликатоа?
3. И как одно помешает другому?
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057517
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Aleksandr Sharahov,

Посмотри код
Скажи, что там не так
Потому что вроде бы всё так

Первый приоритет - это потребление памяти
Производительность - по остаточному принципу
Но 1/4 свободных ячеек - норм
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057521
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
В несколько раз ускорил, когда сделал
Код: pascal
1.
2.
const
  HASH_SHIFT = 4;



Но всё равно мне кажется не то )
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057526
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сделал размер степени двойки

100k в Release считается за 400мс
Вроде неплохо
Но стоит добавить хотя бы по одному дубликату - проседает почти до 2 сек
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057529
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
// http://burtleburtle.net/bob/hash/integer.html
Function BJ32BitFAHash(AValue : Cardinal) : Cardinal;
Begin

 Result := (AValue  +  $7ED55D16)  +  (AValue Shl 12);
 Result := (Result Xor $C761C23C) Xor (Result Shr 19);
 Result := (Result  +  $165667B1)  +  (Result Shl 5);
 Result := (Result  +  $D3A2646C) Xor (Result Shl 9);
 Result := (Result  +  $FD7046C5)  +  (Result Shl 3);
 Result := (Result Xor $B55A4F09) Xor (Result Shr 16);

End;
//



Код: pascal
1.
2.
3.
4.
5.
function HashTableBucket(const P: Pointer; const ACapacity: NativeUInt): NativeUInt; inline;
begin
//  Result := HashTableFixBucket((NativeUInt(P) shr HASH_SHIFT) and ACapacity, ACapacity);
  Result := HashTableFixBucket(BJ32BitFAHash(NativeUInt(P){ shr HASH_SHIFT}) and ACapacity, ACapacity);
end;
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057530
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Kazantsev Alexey,

Да, ты прав
Небо и земля
Может что-то попроще взять?
Типа седжвика, например

Вот неплохой вариант (от hash_table2.zip):
Код: pascal
1.
2.
3.
4.
5.
6.
7.
function HashTableBucket(const P: Pointer; const ACapacityMask: NativeUInt): NativeUInt; inline;
var
  LTemp: NativeUInt;
begin
  LTemp := NativeUInt(P) shr 16;
  Result := ((NativeUInt(P) + LTemp) xor LTemp) and ACapacityMask;
end;
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057533
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

Попроще - это для x86 без хеша вообще, а для x64 брать младшие 4 байта.
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057545
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Что-то я совсем не догоняю

Почему это работает супер быстро:
Код: pascal
1.
2.
3.
4.
function HashTableBucket(const P: Pointer; const ACapacityMask: NativeUInt): NativeUInt; inline;
begin
   Result := NativeUInt(P) and ACapacityMask;
end;


А вот это вот супер медленно:
Код: pascal
1.
2.
3.
4.
function HashTableBucket(const P: Pointer; const ACapacityMask: NativeUInt): NativeUInt; inline;
begin
   Result := (NativeUInt(P) shr 4) and ACapacityMask;
end;



По идее наоборот. Если большинство указателей выровнены на 16 байт, то смещение наоборот должно дать хорошее распределение
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057654
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUВозможны дубликаты (не часто), функция удаления должна возвращать, остался ли дубликат
реализация из дельфи очень плохо ложится на "условие возможности дуликатов" и вообще на "исключающий поиск"
т.е. "спрашивать то чего нет" это очень тормознуто

это возникает из-за того что для разруливания коллизий используется алгоритм "проверять до пустой", а обычно все значения кучкуются (теоретически вероятность встретить значение типа деградирует как (Size/Capacity)^i, но поди найди идеальную хэш-функцию), т.е. проверка наличия обычно деградирует на поиск совпадающего кеша по большому куску таблицы.

лучше поискать реализации с устранением коллизий с помощью бинарных деревьев (есть реализации на плоском массиве, ищи), либо искать очень хорошую хэш-функцию и разряжать массив (Size/Capacity -> 0), что бы дырки были чаще
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057655
Фотография crutchmaster
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

В стандартной либе дельфи нет хеш-таблиц?
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057656
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU
Что-то я совсем не догоняю

Почему это работает супер быстро:
Код: pascal
1.
2.
3.
4.
function HashTableBucket(const P: Pointer; const ACapacityMask: NativeUInt): NativeUInt; inline;
begin
   Result := NativeUInt(P) and ACapacityMask;
end;


А вот это вот супер медленно:
Код: pascal
1.
2.
3.
4.
function HashTableBucket(const P: Pointer; const ACapacityMask: NativeUInt): NativeUInt; inline;
begin
   Result := (NativeUInt(P) shr 4) and ACapacityMask;
end;



По идее наоборот. Если большинство указателей выровнены на 16 байт, то смещение наоборот должно дать хорошее распределение
ничего удивительного здесь нет, значения начинают кучковаться, собираясь в большие блоки
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057664
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Действительно в Delphi поиск до пустого
Я же ищу только в рамках бакета

Насчёт кучковаться. Наоборот скученность должна давать много коллизий
Соответственно быть медленной
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057677
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU
kealon(Ruslan),

Действительно в Delphi поиск до пустого
Я же ищу только в рамках бакета

Насчёт кучковаться. Наоборот скученность должна давать много коллизий
Соответственно быть медленной
я о чём и говорю, второй вариант даёт большие кучки
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057713
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Робин Гуд очень хорошо помогает в ситуации, когда в таблице формируются большие кластеры. Заполненность таблицы можно довести до 0.99 (если правильно помню, в расте хеш-таблицы с робин гудом, и там 0.9 по дефолту). Когда я с ним экспериментировал на обычных данных, то не впечатлился (скорость вставки несколько снижается, по сравнению с линейной схемой), однако, впоследствии, встретил ситуацию, при которой таблица без робин гуда адово тормозила.
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057740
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Kazantsev Alexey,

Тормозит на 20-30% потому, что "скакать надо" по массиву в произвольном порядке
реализации с деревьми тоже такой-же эффект имеют на современных процах, но они не дают обвалиться производительности на плохих случаях - за всё надо платить. В той же jave хвалятся, что реализовали устранение коллизий через бинарное дерево, но факт выше как-то умалчивают, и про перерасход памяти раза в 3 тоже молчат.

Можно бы было как вариант поддерживать сортировку по (Hash & C) - это бы позволило не гулять по всему куску при удалении и поиске, но он уже таблицу хешей срезал, что разумно.
В его случае, что бы побороться за производительность придётся пустить съэкономленную память на "слабое заполнение".
"Открытая адресация" других вариантов не оставляет. Ну и хорошо подбирать хэш-функцию под ситуацию.
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057789
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)
Тормозит на 20-30%

Не совсем понял, что тормозит?
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057826
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Kazantsev Alexey
kealon(Ruslan)
Тормозит на 20-30%

Не совсем понял, что тормозит?
по сравнению со стандартной реализаций, например как в TDictionary
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057829
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

В смысле, робингуд тормозит?
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057841
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Kazantsev Alexey,

да, так же как и 2-choice hashing
чем тупее код, тем он в идеальном варианте быстрее (при прочих равных естественно: хэш-функция, ёмкость)
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057850
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Ты чего-то путаешь. У робингуда на вставке снижение очень незначительное, ни о каких десятках процентов нет и речи.
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057866
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

И ещё на счёт:
kealon(Ruslan)
"спрашивать то чего нет" это очень тормознуто


Берём таблицу с робингудом на 300K элементов (указателей). Примем условие, что только 30% ключей являются уникальными. Усложним задачу, установив заполнение таблицы: 0.99
Код: plaintext
1.
2.
3.
4.
add: 169             -- Вставляем 100K ключей 3 траза.
contains: 672       -- Ищем 100K существующих ключей 3 раза.
not contains: 688 -- Ищем 100K несуществующих ключей 3 раза.
remove: 924        -- Удаляем 100K существующих ключей 3 раза.

Как видим, поиск существующих и не существующих ключей выполняется за одинаковое время. Теперь посмотрим как выглядит эта таблица:

Код: plaintext
1.
2.
3.
4.
5.
6.
# Hash table map
# ==============
# Items.Length : 303030
# Capacity     : 300000 (2147483647)
# Count        : 300000
# Load Factor  : 0.9900 (0.9900)
...
Рейтинг: 0 / 0
Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
    #40057872
Aleksandr Sharahov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Kazantsev Alexey,

при поиске всегда выдается первый найденный?

вставляем 3 раза по 3 дубликата - непонятно как выглядят заголовки процедур?
...
Рейтинг: 0 / 0
25 сообщений из 62, страница 1 из 3
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Linear probing хеш-таблица с дубликатами (просьба подключиться А. Шарахова)
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]