Multicore programming

Slides:



Advertisements
Similar presentations
Automated Theorem Proving Lecture 1. Program verification is undecidable! Given program P and specification S, does P satisfy S?
Advertisements

Singly linked lists Doubly linked lists
Wait-Free Linked-Lists Shahar Timnat, Anastasia Braginsky, Alex Kogan, Erez Petrank Technion, Israel Presented by Shahar Timnat 469-+
Chapter 1 Object Oriented Programming 1. OOP revolves around the concept of an objects. Objects are created using the class definition. Programming techniques.
Operating Systems Part III: Process Management (Process Synchronization)
Alon Efrat Computer Science Department University of Arizona SkipList.
Data Structures: A Pseudocode Approach with C
Binary Search Trees Briana B. Morrison Adapted from Alan Eugenio.
Introduction to Lock-free Data-structures and algorithms Micah J Best May 14/09.
Linked Lists. Example We would like to keep a list of inventory records – but only as many as we need An array is a fixed size Instead – use a linked.
Chapter 3: Arrays, Linked Lists, and Recursion
Data Structures Week 6 Further Data Structures The story so far  We understand the notion of an abstract data type.  Saw some fundamental operations.
Computer Science Department Data Structure & Algorithms Lecture 8 Recursion.
Chapter 1 Object Oriented Programming. OOP revolves around the concept of an objects. Objects are created using the class definition. Programming techniques.
11/18/20151 Operating Systems Design (CS 423) Elsa L Gunter 2112 SC, UIUC Based on slides by Roy Campbell, Sam.
Formal verification of skiplist algorithms Student: Trinh Cong Quy Supervisor: Bengt Jonsson Reviewer: Parosh Abdulla.
“Never doubt that a small group of thoughtful, committed people can change the world. Indeed, it is the only thing that ever has.” – Margaret Meade Thought.
A Simple Optimistic skip-list Algorithm Maurice Herlihy Brown University & Sun Microsystems Laboratories Yossi Lev Brown University & Sun Microsystems.
Hashtables. An Abstract data type that supports the following operations: –Insert –Find –Remove Search trees can be used for the same operations but require.
© Love Ekenberg Hashing Love Ekenberg. © Love Ekenberg In General These slides provide an overview of different hashing techniques that are used to store.
1. Circular Linked List In a circular linked list, the last node contains a pointer to the first node of the list. In a circular linked list,
Chapter 5 Linked List by Before you learn Linked List 3 rd level of Data Structures Intermediate Level of Understanding for C++ Please.
Copyright © 2012 Pearson Education, Inc. Chapter 20: Binary Trees.
Copyright © 2015, 2012, 2009 Pearson Education, Inc., Publishing as Addison-Wesley All rights reserved. Chapter 20: Binary Trees.
Circular linked list A circular linked list is a linear linked list accept that last element points to the first element.
An algorithm of Lock-free extensible hash table Yi Feng.
Linked list: a list of items (nodes), in which the order of the nodes is determined by the address, called the link, stored in each node C++ Programming:
Lists List Implementations. 2 Linked List Review Recall from CMSC 201 –“A linked list is a linear collection of self- referential structures, called nodes,
Copyright © 2009 Pearson Education, Inc. Publishing as Pearson Addison-Wesley Chapter 20: Binary Trees.
Arrays, Link Lists, and Recursion Chapter 3. Sorting Arrays: Insertion Sort Insertion Sort: Insertion sort is an elementary sorting algorithm that sorts.
Data Structures and Algorithm Analysis Dr. Ken Cosh Linked Lists.
Chapter 3 Lists, Stacks, Queues. Abstract Data Types A set of items – Just items, not data types, nothing related to programming code A set of operations.
Lecture 6 of Computer Science II
Review Array Array Elements Accessing array elements
Håkan Sundell Philippas Tsigas
Lecture 5: Synchronization
Doubly Linked List Review - We are writing this code
UNIT-3 LINKED LIST.
Week 8 - Wednesday CS221.
Atomic Operations in Hardware
Algorithm for deleting a node from a singly linked list
EEL 4854 IT Data Structures Linked Lists
Queues Queues Queues.
Handling Collisions Open Addressing SNSCT-CSE/16IT201-DS.
Distributed Algorithms (22903)
Linked Lists head One downside of arrays is that they have a fixed size. To solve this problem of fixed size, we’ll relax the constraint that the.
Linked lists Motivation: we can make arrays, but their functionality is slightly limited and can be difficult to work with Biggest issue: size management.
Distributed Algorithms (22903)
Chapter 20: Binary Trees.
Prof. Neary Adapted from slides by Dr. Katherine Gibson
Chapter 21: Binary Trees.
Concurrent Data Structures Concurrent Algorithms 2017
Lock-Free concurrent algorithm for Linked lists: Implementation
Linked Lists.
Multicore programming
Multicore programming
Multicore programming
A Hash Table with Chaining
Chapter 16 Linked Structures
Multicore programming
A Concurrent Lock-Free Priority Queue for Multi-Thread Systems
Multicore programming
Multicore programming
Multicore programming
Multicore programming
Data Structures & Algorithms
Multicore programming
Multicore programming
Linked Lists.
EECE.3220 Data Structures Instructor: Dr. Michael Geiger Spring 2019
Presentation transcript:

Multicore programming High level synchronization primitives Week 4 – Monday Trevor Brown http://tbrown.pro

Last time Implementation of hash table with expansion Making migration/expansion more efficient Lock-free singly-linked lists (using marking) Difficulty of node-based data structures Preventing changes to deleted nodes Atomically modifying two or more variables (Especially hard if some operations are lock-free)

Lock-free Bi-directional searches complicate linearization Insert(k): Search without locking until we reach nodes pred & succ where pred.key < k <= succ.key If we found k, return false Lock pred, lock succ If pred.next != succ, unlock and retry Create new node n pred.next = n succ.prev = n Unlock all Where should we linearize a successful insert? Case 1: linearize here Case 2: linearize here Insert(k) was not linearized yet: should NOT find k! Insert(k) was linearized already: should find k! pred.next = n succ.prev = n thread p Insert(k) thread q SearchL(k) Does NOT find k thread r SearchR(k) Finds k time

Making two changes appear atomic Something stronger than CAS? Double compare-and-swap (DCAS) Like CAS, but on any two memory locations DCAS(addr1, addr2, exp1, exp2, new1, new2) Not implemented in modern hardware But we can implement it in software, using CAS!

DCAS object Usage - addresses that are modified by DCAS: DCAS(addr1, addr2, exp1, exp2, new1, new2) atomic { if (*addr1 == exp1 && *addr2 == exp2) { *addr1 = new1; *addr2 = new2; return true; } else return false; } Usage - addresses that are modified by DCAS: must not be modified with writes/CAS must be read using DCASRead DCASRead(addr) return the value last stored in *addr by a DCAS

DCAS-based doubly-linked list Add sentinel nodes to avoid edge cases when list is empty Consequence: never update head or tail pointers Use DCAS to change pointers (but not keys) Consequence: must use DCASRead to read pointers (but not keys) Note: no need to read head or tail with DCASRead! head tail −∞ 15 20 +∞

First attempt at an Implementation Contains(23) pair<node, node> InternalSearch(key_t k) 1 pred = head 2 succ = head 3 while (true) 4 if (succ == NULL or succ.key >= k) 5 return make_pair(pred, succ); 6 pred = succ; 7 succ = DCASRead(succ.next); succ succ succ succ −∞ 15 20 27 +∞ InternalSearch returns pointers to these Contains(23) sees succ.key != k, and returns false bool Contains(key_t k) 8 pred, succ = InternalSearch(k); 9 return (succ.key == k); InternalSearch postcondition: pred.key < k ≤ succ.key

First attempt at an Implementation bool Insert(key_t k) pred succ 10 while (true) 11 pred, succ = InternalSearch(k); 12 if (succ.key == k) return false; 13 n = new node(k); 14 if (DCAS(&pred.next, &succ.prev, succ, pred, n, n)) 15 return true; 16 else delete n; 15 20 n 17 bool Delete(key_t k) 17 while (true) 18 pred, succ = InternalSearch(k); 19 if (succ.key != k) return false; 20 after = DCASRead(succ.next); 21 if (DCAS(&pred.next, &after.prev, succ, succ, after, pred)) 22 return true; // not covered: freeing succ pred succ after 15 17 20

Is this algorithm correct? DCAS helps with this Recall: main difficulties in node-based data structures Atomically modifying two or more variables Preventing changes to deleted nodes Can we argue deleted nodes don’t get changed? pred succ after 15 17 20 Observation: No node points to succ once it is deleted Invariant: no node points to a deleted node Plausible lemma: whenever we change a node, another node points to it

Can we prove the plausible lemma? The DCAS in Insert succeeds and changes pred and succ only if they point to each other! Plausible lemma: whenever we change a node, another node points to it bool Insert(key_t k) pred succ 10 while (true) 11 pred, succ = InternalSearch(k); 12 if (succ.key == k) return false; 13 n = new node(k); 14 if (DCAS(&pred.next, &succ.prev, succ, pred, n, n)) 15 return true; 16 else delete n; 15 20 n 17 The DCAS in Delete succeeds and changes pred and after if they both point to succ… bool Delete(key_t k) 17 while (true) 18 pred, succ = InternalSearch(k); 19 if (succ.key != k) return false; 20 after = DCASRead(succ.next); 21 if (DCAS(&pred.next, &after.prev, succ, succ, after, pred)) 22 return true; // not covered: freeing succ pred succ after 15 17 20 Could maybe succeed even if nothing points to pred or after!

Thread p: start Delete(20), find pred, succ, after A Counter example Thread p: start Delete(20), find pred, succ, after Thread p: sleep just before executing DCAS(&pred.next, &after.prev, succ, succ, after, pred) Thread q: Delete(17) Thread q: Delete(25) Thread p: DCAS succeeds, modifying deleted nodes! Delete(20) returns true, but 20 is not deleted! pred succ after −∞ 15 17 20 25 27 +∞

Overcoming this problem: marking Recall: marking can prevent changes to deleted nodes How to atomically change two pointers AND mark other pointers/nodes using DCAS? Use an even stronger primitive… k-word compare-and-swap (KCAS) Like a CAS that atomically operations on k memory addresses Can be implemented in software from CAS

kCAS object: Making k changes appear atomic Operations KCAS(addr1..addrk, exp1..expk, new1..newk) Atomically: If all addresses contain their expected values, sets all addresses to their new values and return true else return false KCASRead(addr): return the last value stored in addr by a KCAS Usage - addresses that are modified by KCAS: must not be modified with writes/CAS must be read using KCASRead

KCAS-based doubly-linked list pred succ after Based on our attempt using DCAS When deleting a node, use KCAS to also mark that node When modifying or deleting any node, use KCAS to verify the node is not marked Note: since we use KCAS to mark nodes, we must use KCASRead to read marks Delete(17) 15 17 20 X pred succ Insert(17) 15 20 n 17 pred succ after KCAS fails! Delete(17) 15 17 20 X

Implementation using KCAS pair<node, node> InternalSearch(key_t k) 1 pred = head 2 succ = head 3 while (true) 4 if (succ == NULL or succ.key >= k) 5 return make_pair(pred, succ); 6 pred = succ; 7 succ = KCASRead(succ.next); bool Contains(key_t k) 8 pred, succ = InternalSearch(k); 9 return (succ.key == k);

Implementation using KCAS bool Insert(key_t k) pred succ 10 while (true) 11 pred, succ = InternalSearch(k); 12 if (succ.key == k) return false; 13 n = new node(k); 14 if (KCAS(&pred.mark, &succ.mark, &pred.next, &succ.prev, false, false, succ, pred, false, false, n, n)) 15 return true; 16 else delete n; 15 20 n 17

Implementation using KCAS bool Delete(key_t k) 17 while (true) 18 pred, succ = InternalSearch(k); 19 if (succ.key != k) return false; 20 after = KCASRead(succ.next); 21 if (KCAS(&pred.mark, &succ.mark, &after.mark, &pred.next, &after.prev, false, false, false, succ, succ, false, true, false, after, pred)) 22 return true; // not covered: freeing succ pred succ after 15 17 20 X

Is this algorithm correct? KCAS makes this easy Main challenges Atomically modifying two or more variables Preventing changes to deleted nodes Let’s sketch the correctness argument… Marking (with KCAS) makes this easy

The difficult argument: Linearizing contains pair<node, node> InternalSearch(key_t k) 1 pred = head 2 succ = head 3 while (true) 4 if (succ == NULL or succ.key >= k) 5 return make_pair(pred, succ); 6 pred = succ; 7 succ = KCASRead(succ.next); Where to linearize Contains that returns true? Prove there exists a time during InternalSearch when succ is in the list, and linearize then bool Contains(key_t k) Where to linearize Contains that returns false? 8 pred, succ = InternalSearch(k); 9 return (succ.key == k); Prove there exists a time during InternalSearch when: pred and succ were both in the list and pred.next = succ. Linearize then.

A contains that returns true Observation: we reached succ by following the pointer pred.next Case 1: Suppose when we followed pred.next, pred was in the list At that time, succ was in the list, and keys never change, so succ.key == k at that time – linearize then! Case 2: Suppose when we followed pred.next, pred was already deleted Lemma: pred was deleted during our InternalSearch (or else we could not reach it) Since nodes are not changed after they are deleted, pred.next must have pointed to succ just before it was deleted, which was during our InternalSearch – linearize at that time! Prove there exists a time during InternalSearch when succ is in the list, and linearize then To be theoretically rigorous here, need an inductive proof that each node you found was in the list at some time during your InternalSearch

A contains that returns false Observation: we reached succ by following the pointer pred.next Case 1: Suppose when we followed pred.next, pred was in the list At that time, pred and succ were both in the list, and keys never change, so pred.key < k <= succ.key) – linearize at that time! Case 2: Suppose when we followed pred.next, pred was already deleted Lemma: pred was deleted during our InternalSearch (or else we could not reach it) Since nodes are not changed after they are deleted, pred.next must have pointed to succ just before it was deleted, which was during our InternalSearch – linearize at that time! Prove there exists a time during InternalSearch when: pred and succ were both in the list and pred.next = succ. Linearize then.

Linearizing insert bool Insert(key_t k) 10 while (true) 11 pred, succ = InternalSearch(k); 12 if (succ.key == k) return false; 13 n = new node(k); 14 if (KCAS(&pred.mark, &succ.mark, &pred.next, &succ.prev, false, false, succ, pred, false, false, n, n)) 15 return true; 16 else delete n; Where to linearize Insert that returns false? Where to linearize Insert that returns true? Prove there exists a time during InternalSearch when succ was in the list, and linearize then (same argument as Contains returning true) At its successful KCAS

Linearizing Delete bool Delete(key_t k) 17 while (true) 18 pred, succ = InternalSearch(k); 19 if (succ.key != k) return false; 20 after = KCASRead(succ.next); 21 if (KCAS(&pred.mark, &succ.mark, &after.mark, &pred.next, &after.prev, false, false, false, succ, succ, false, true, false, after, pred)) 22 return true; // not covered: freeing succ Where to linearize Delete that returns false? Where to linearize Delete that returns true? Prove there exists a time during InternalSearch when: pred and succ were both in the list and pred.next = succ. (same argument as Contains returning false) At its successful KCAS

Next time How do you implement KCAS? Is it practical (efficient/fast)? If not, can you make it practical?