Я пытаюсь написать потокобезопасный отсортированный один связанный список .Я написал две версии: грубая и точная синхронизация.Вот две реализации:
Мелкозернистый:
public void add(T t) {
Node curr = head;
curr.lock.lock();
while (curr.next != null) {
// Invariant: curr is locked
// Invariant: curr.data < t
curr.next.lock.lock();
if (t.compareTo(curr.next.data) <= 0) {
break;
}
Node tmp = curr.next;
curr.lock.unlock();
curr = tmp;
}
// curr is acquired
curr.next = new Node(curr.next, t);
if (curr.next.next != null) { // old curr's next is acquired
curr.next.next.lock.unlock();
}
curr.lock.unlock();
}
Крупнозернистый:
public void add(T t) {
lock.lock();
Node curr = head;
while (curr.next != null) {
if (t.compareTo(curr.next.data) <= 0) {
break;
}
curr = curr.next;
}
curr.next = new Node(curr.next, t);
lock.unlock();
}
Я рассчитал две версии на 4 потока (на 4 логических ядрах процессора)) вставив 20000 целых чисел.Время на поток показывает время ЦП (т.е. оно не включает время ожидания).
Fine grained:
Worked 1 spent 1080 ms
Worked 2 spent 1230 ms
Worked 0 spent 1250 ms
Worked 3 spent 1260 ms
wall time: 1620 ms
Coarse grained:
Worked 1 spent 190 ms
Worked 2 spent 270 ms
Worked 3 spent 410 ms
Worked 0 spent 280 ms
wall time: 1298 ms
Сначала я думал, что проблемы .lock()
и .unlock()
, но я профилировал реализацию, и вместе онипотребляется только 30% времени.Мое второе предположение состоит в том, что мелкозернистое решение имеет больше пропусков кэша, но я сомневаюсь в этом, потому что один связанный список, в отличие от массива, по своей природе склонен к промахам кэша.
Любая идея, почему я не получаюожидаемое распараллеливание?