GithubHelp home page GithubHelp logo

litl's Introduction

LiTL: Library for Transparent Lock interposition

LiTL is a library that allows executing a program based on Pthread mutex locks with another locking algorithm.

  • Author : Hugo Guiroux <hugo.guiroux at gmail dot com>
  • Related Publication: Multicore Locks: the Case is Not Closed Yet, Hugo Guiroux, Renaud Lachaize, Vivien Quéma, USENIX ATC'16.

Building

make

Dependencies

  • numactl
  • coreutils
  • make
  • gcc
  • git

Execution

Launch your application using one of the provided scripts. The name of the script to you use depends on the chosen lock.

Examples:

  • If you want to use the MCS lock with the spin-then-park policy, do the following: ./libmcs_spin_then_park.sh my_program

  • For the CLH lock with the spin policy, do the following: ./libclh_spinlock.sh my_program

  • For the ticket lock (which has its waiting policy hardcoded - see below), do the following: ./libticket_original.sh my_program

Details

Usage

The algorithms are named according to the following schema: lib{algo}_{waiting_policy}.sh.

The waiting policy can either be spinlock, spin_then_park or original.

Some algorithms come with different waiting policies (Malthusian, MCS, C-BO-MCS, CLH). For example, if you want to execute your application with the MCS lock, using a spin-then-park waiting policy, use libmcs_spinlock.sh.

For the other algorithms, the name of the script to use is of the form lib{algo}_original.sh. In this case, the waiting policy depends on the one used in the original design of the corresponding lock (spin in most cases).

The library uses LD_PRELOAD to intercept calls to most of the pthread_mutex_* functions.

Supported algorithms

Name Ref Waiting Policy Supported Name in the Paper [LOC] Notes and acknowledgments
ALOCK-EPFL [AND] original (spin) alock-ls From libslock
Backoff [MCS] original (spin) backoff From concurrencykit
C-BO-MCS [COH] spinlock or spin-then-park c-bo-mcs_spin and c-bo-mcs_stp
CLH [SYN] spinlock of spin-then-park clh_spin and clh_stp
CLH-EPFL [SYN] original (spin) clh-ls From libslock
C-PTL-TKT [COH] original (spin) c-ptl-tkt
C-TKT-TKT [COH] original (spin) c-tkt-tkt
HMCS [HMC] original (spin) hmcs
HT-LOCK-EPFL [EVR] original (spin) hticket-ls From libslock
HYS-HMCS [HYS] original (spin) ahmcs
Malthusian [MAL] spinlock or spin-then-park malth_spin and malth_stp This is the Malthusian-MCS version.
Mutexee [MUT] original (spin_then_park) mutexee From lockin
MCS [MCS] spinlock or spin-then-park mcs_spin and mcs_stp From RCL
MCS-EPFL [MCS] original (spin) mcs-ls From libslock
MCS-TP [PRE] original (spin hybrid) mcs-timepub From RCL
Partitioned [PAR] original (spin) partitioned
Pthread-Adaptive [ADP] original (spin_then_park) pthreadadapt Wrapper around pthread lock with adaptive policy
Pthread-Interpose - original (park) pthread Wrapper around classic pthread lock
Spinlock [SYN] original (spin) spinlock
Spinlock-EPFL [SYN] original (spin) spinlock-ls From libslock
Ticket [MCS] original (spin) ticket From lockless
Ticket-EPFL [MCS] original (spin) ticket-ls From libslock
TTAS [AND] original (spin) ttas
TTAS-EPFL [AND] original (spin) ttas-ls From libslock

Note that the pthread-adaptive and pthread-interpose wrappers are provided only for fair comparison with the other algorithms (i.e., to introduce the same library interposition overhead).

Support for condition variables

Summary of the approach

As explained in [LOC], we rely on classic Pthread condition variables to implement condition variables. Here is some pseudo-code to summarize the approach:

// return values and error checks
// omitted for simplification

pthread_mutex_lock(pthread_mutex_t *m) {
    optimized_mutex_t *om;
    om = get_optimized_mutex(m);
    if (om == null) {
        om = create_and_store_optim_mutex(m);
    }
    optimized_mutex_lock(om);
    real_pthread_mutex_lock(m);
}

pthread_mutex_unlock(pthread_mutex_t *m) {
    optimized_mutex_t *om;
    om = get_optimized_mutex(m);
    optimized_mutex_unlock(om);
    real_pthread_mutex_unlock(m);
}

pthread_cond_wait(pthread_cond_t *c,
                  pthread_mutex_t *m) {
    optimized_mutex_t *om;
    om = get_optimized_mutex(m);
    optimized_mutex_unlock(om);
    real_pthread_cond_wait(c, m);
    real_pthread_mutex_unlock(m);
    optimized_mutex_lock(om);
    real_pthread_mutex_lock(m);
}

// Note that the pthread_cond_signal and
// pthread_cond_broadcast primitives
// do not need to be interposed

This strategy does not introduce contention on the Pthread lock, as the latter is only requested by the holder of the optimized lock associated with the critical section.

Some people have raised concerns about the possibility that several threads contended on the Pthread lock, especially for workloads using pthread_cond_broadcast. However, on Glibc/Linux, pthread_cond_broadcast is implemented (via the futex syscall) in a way such that it does not wake up several threads at the same time. Indeed, according to the source code of the pthread_cond_broadcast implementation, the broadcast function simply wakes up a single thread from the wait queue of the condition variable and transfers the remaining threads to the wait queue of the Pthread lock. This is also confirmed in the futex(2) man page. So, overall, for workloads that use pthread_cond_broadcast and/or pthread_cond_signal, it is unlikely to have more than two threads contending for the Pthread lock at the same time.

Disabling support for condition variables

By default, the locks are built with the above-described support for condition variables. However, if you know that the target application does not use condition variables, you can build the locks without it. This allows optimizing the critical path for lock acquisition/release by removing the need to acquire/release the underlying Pthread lock. To do so, use make no_cond_var.

Trylock primitives

Implementation

Some algorithms do not come officially with a trylock primitive (i.e., non blocking lock request). Here is how we implemented trylock for them :

  • Cohorting: first trylock the local lock, and if needed trylock the top lock. If acquiring the top lock fails, unlock the local lock.
  • HMCS: same idea as the one for the cohorting locks
  • HYS-HMCS: as the algorithm supports fast-path, we only trylock the root MCS lock.
  • Malthusian: just trylock like with a classical MCS lock.
  • Ticket lock: the grant and request tickets are two 32-bit consecutive integers that are considered as a single 64-bit integer when trylocking.
  • Partitioned Ticket: we cannot use the trick of the ticket lock because there are only 64-bit atomic ops and we have an array of tickets. So we first check if there is anybody waiting for the lock and if not, we try to lock (we may wait a little if threads come between the check and the acquisition).

Unsupported locks

To the best of our knowledge, the design of these algorithms does not support trylock semantics:

  • CLH and CLH-EPFL
  • HTLOCK-EPFL

Adding a new lock

The library is designed to be extensible. In order to introduce a new lock algorithm, consider the following steps:

  1. Edit Makefile.config: add one line with the format algo_waitingstrategy (algo without spaces in lowercase, waitingstrategy is original, spinlock or spin_then_park)
  2. Add a file include/algo.h: take spinlock as an example
  3. Add a file src/algo.c: take spinlock as an example
  4. Modify the top of the interpose.c file to add a #ifdef for your algorithm (the Makefile takes care of everything)

Remarks:

  • Several helper functions are available in src/utils.c and src/utils.h.
  • If each thread needs its context for a lock, see include/mcs.h (#define NEED_CONTEXT 1 is important)
  • If you want to automatically support different waiting policies, use #define SUPPORT_WAITING 1 and waiting_policy_{sleep/wake}. Look into src/mcs.c for an example.
  • There is an example of a non-lock (src/concurrency.c) to show a case where the library can be used for logging statistics about locks (instead of replacing the original lock algorithm).

Cascading interposition libraries

You may want to capture statistics for different locks. For example, if you want to capture the concurrency of the MCS algorithm, you can do the following:

./libconcurrency_original.sh ./libmcs_spinlock.sh my_program

Details

In order to be able to chain interposition libraries, we must add versions to the symbols we export. This is done using a symbol map (see src/interpose.map) and by adding a symver asm symbol after the function declaration (see src/interpose.c). Without that, the library is not able to get the function pointer address of the next function using dlvsym.

References and acknowledgments

Lock algorithms

  • [ADP] Kaz Kylhyky. 2014. What is PTHREAD_MUTEX_ADAPTIVE_NP? http://stackoverflow.com/a/25168942
  • [AND] Thomas E. Anderson. 1990. The Performance of Spin Lock Alternatives for Shared-Memory Multiprocessors. IEEE Trans. Parallel Distrib. Syst. 1, 1 (January 1990).
  • [COH] Dave Dice, Virendra J. Marathe, and Nir Shavit. 2015. Lock Cohorting: A General Technique for Designing NUMA Locks. ACM Trans. Parallel Comput. 1, 2, Article 13 (February 2015).
  • [EVR] Tudor David, Rachid Guerraoui, and Vasileios Trigonakis. 2013. Everything you always wanted to know about synchronization but were afraid to ask. In Proceedings of the Twenty-Fourth ACM Symposium on Operating Systems Principles (SOSP '13).
  • [HMC] Milind Chabbi, Michael Fagan, and John Mellor-Crummey. 2015. High performance locks for multi-level NUMA systems. In Proceedings of the 20th ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming (PPoPP 2015).
  • [HYS] Milind Chabbi and John Mellor-Crummey. 2016. Contention-conscious, locality-preserving locks. In Proceedings of the 21st ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming (PPoPP '16).
  • [LOC] Hugo Guiroux, Renaud Lachaize, and Vivien Quéma. 2016. Multicore Locks: the Case is Not Closed Yet (USENIX ATC'16).
  • [MAL] Dave Dice. 2015. Malthusian Locks. In CoRR (arXiv).
  • [MUT] Babak Falsafi, Rachid Guerraoui, Javier Picorel, and Vasileios Trigonakis. 302+. Unlocking Energy (USENIX ATC'16).
  • [MCS] John M. Mellor-Crummey and Michael L. Scott. 1991. Algorithms for scalable synchronization on shared-memory multiprocessors. ACM Trans. Comput. Syst. 9, 1 (February 1991).
  • [PAR] David Dice. 2011. Brief announcement: a partitioned ticket lock. In Proceedings of the twenty-third annual ACM symposium on Parallelism in algorithms and architectures (SPAA '11)
  • [PRE] Bijun He, William N. Scherer, and Michael L. Scott. 2005. Preemption adaptivity in time-published queue-based spin locks. In Proceedings of the 12th international conference on High Performance Computing (HiPC'05)
  • [RCL] Jean-Pierre Lozi, Florian David, Gaël Thomas, Julia Lawall, and Gilles Muller. 2016. Fast and Portable Locking for Multicore Architectures. ACM Trans. Comput. Syst. 33, 4, Article 13 (January 2016)
  • [SYN] Michael L. Scott. 2013. Shared-Memory Synchronization. Morgan & Claypool Publishers.

Implementations

Some lock implementations are borrowed (fully or partially) from source code repositories developed by other people. While we try to cite the authors of the original implementation (and the corresponding license) at the beginning of each file, we may have made mistakes and omissions. Please contact us if you notice any issue.

Sources:

LiTL also uses the CLHT hashtable. This hashtable is used to link a pthread_mutex_lock with the underlying data structure of the interposed lock (e.g., MCS).

litl's People

Contributors

cota avatar hugoguiroux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

litl's Issues

segmentation fault

When I try to run a few tests in the sample directory in upscaledb with a litl script, I get a segment error, but I have no problem running the leveldb db_bench benchmark. How do I fix this problem? Please note that I did not make any code changes while testing.

Errors are as follows:
node1:~/lym/upscaledb$ /home/hpc/litl_ori/litl/libmcs_spinlock.sh ./samples/db1
/home/hpc/litl_ori/litl/libmcs_spinlock.sh: line 33: 151635 Segmentation fault (core dumped)LD_PRELOAD=$LD_PRELOAD:$BASE/lib/libmcs_spinlock.so "$@"
and i use gdb find that:
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x00007ff5717e7a20 in mcs_mutex_create (attr=attr@entry=0x0) at mcs.c:70
#2 0x00007ff5717e6d5f in ht_lock_create (attr=0x0, mutex=0x560a976f2000) at interpose.c:141
#3 pthread_mutex_init (mutex=0x560a976f2000, attr=0x0) at interpose.c:423
#4 0x00007ff56ee40ac8 in google::protobuf::DescriptorPool::DescriptorPool(google::protobuf::DescriptorDatabase*, google::protobuf::DescriptorPool::ErrorCollector*) () from /usr/lib/x86_64-linux-gnu/libprotobuf.so.10
#5 0x00007ff56ee40ba0 in ?? () from /usr/lib/x86_64-linux-gnu/libprotobuf.so.10
#6 0x00007ff56ee0b73a in google::protobuf::GoogleOnceInitImpl(long*, google::protobuf::Closure*) () from /usr/lib/x86_64-linux-gnu/libprotobuf.so.10
#7 0x00007ff56ee386df in google::protobuf::DescriptorPool::InternalAddGeneratedFile(void const*, int) ()
from /usr/lib/x86_64-linux-gnu/libprotobuf.so.10
#8 0x00007ff56ee2f914 in google::protobuf::protobuf_AddDesc_google_2fprotobuf_2fany_2eproto() () from /usr/lib/x86_64-linux-gnu/libprotobuf.so.10
#9 0x00007ff5719ff8d3 in call_init (env=0x7ffd9fa2a358, argv=0x7ffd9fa2a348, argc=1, l=) at dl-init.c:72
#10 _dl_init (main_map=0x7ff571c1a170, argc=1, argv=0x7ffd9fa2a348, env=0x7ffd9fa2a358) at dl-init.c:119
#11 0x00007ff5719f00ca in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#12 0x0000000000000001 in ?? ()
#13 0x00007ffd9fa2b372 in ?? ()
---Type to continue, or q to quit---
it seems that a segment error occurred while calling real_pthread_init in the mcs_mutex_create function ,how should i fix this problem?

Compile Litl library on ARM architecture

Hi @HugoGuiroux
I'm trying to compile Litl library on ARM architecture and replace #include <xmmintrin.h> header file with sse2neon
(sse2neon see https://github.com/DLTcollab/sse2neon/)
The error encountered in CLHT/include/atiomic_ops.h is as follows:
error: impossible constraint in ‘asm’ asm volatile("xchgb %0,%1"
How to solve the above problems to complete the compilation and operation of Litl on the ARM architecture.
Please let me know if you have a solution, thanks a lot.

malthusian 的spinlock版本实现错误

malthusian的spinlock版本后背队列不会进入到睡眠状态,与文章不符。
这里提供一个简单的malthusian 的实现
/*

  • The MIT License (MIT)
  • Copyright (c) 2016 Hugo Guiroux <hugo.guiroux at gmail dot com>
  • Permission is hereby granted, free of charge, to any person obtaining a copy
  • of his software and associated documentation files (the "Software"), to deal
  • in the Software without restriction, including without limitation the rights
  • to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  • copies of the Software, and to permit persons to whom the Software is
  • furnished to do so, subject to the following conditions:
  • The above copyright notice and this permission notice shall be included in
  • all copies or substantial portions of the Software.
  • THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  • IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  • FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  • AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  • LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  • OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  • SOFTWARE.
  • Dave Dice. 2015.
  • Malthusian Locks.
  • In CoRR (arXiv).
  • Idea: this is a classical MCS lock, but to avoid contention, we allow
  • ourselves to modify the waiting queue to
  • put asides some threads for some times.
  • The fairness is ensured by randomly putting back the "asides" threads into
  • the active waiting queue of the lock.
  • Note: this version has been validated by the author.
    */
    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <stdio.h>
    #include <errno.h>
    #include <sys/mman.h>
    #include <pthread.h>
    #include <assert.h>
    #include <unistd.h>
    #include <papi.h>
    #include <malthusian.h>
    // #include <linux/futex.h>
    #include "waiting_policy.h"
    #include "interpose.h"
    #include "utils.h"
    #define COND_VAR 1
    extern __thread unsigned int cur_thread_id;
    int len = 0;
    // From D.Dice https://blogs.oracle.com/dave/entry/a_simple_prng_idiom

static inline uint32_t xor_random() {
static __thread uint32_t rv = 0;

if (rv == 0)
    rv = cur_thread_id + 1;

uint32_t v = rv;
v ^= v << 6;
v ^= (uint32_t)(v) >> 21;
v ^= v << 7;
rv = v;
// printf("random %d\n", v & (UNLOCK_COUNT_THRESHOLD - 1));
return v & (UNLOCK_COUNT_THRESHOLD - 1);

}

malthusian_mutex_t *malthusian_mutex_create(const pthread_mutexattr_t *attr) {
malthusian_mutex_t *impl =
(malthusian_mutex_t *)alloc_cache_align(sizeof(malthusian_mutex_t));
impl->tail = 0;
impl->passive_set_head = 0;
impl->passive_set_tail = 0;
#ifdef COND_VAR
REAL(pthread_mutex_init)(&impl->posix_lock, attr);
#endif

return impl;

}

static int __malthusian_mutex_lock(malthusian_mutex_t *impl,
malthusian_node_t *me) {
malthusian_node_t *tail;
assert(me != NULL);

me->next = 0;
me->spin = LOCKED;
me->tid = cur_thread_id;
tail = __atomic_exchange_n(&impl->tail, me, __ATOMIC_RELEASE);
/* No one there? */
if (!tail) {
    goto succ;
}

/* Someone there, need to link in */
// tail->next = me;
// COMPILER_BARRIER();
// printf("tid %d in queue %d\n", me->tid, me->spin);
 __atomic_store_n(&tail->next, me, __ATOMIC_RELEASE);
/* Spin on my spin variable */
// waiting_policy_sleep(&me->spin);
me->status = S_SPINING;
while (me->spin == 0) {
	CPU_PAUSE();
	if (me->status == S_PARKING) {
        // printf("tid %d sleep\n", me->tid);
        waiting_policy_sleep(&me->status);
        // printf("tid %d waked\n", me->tid);
    }
			
}

succ:
// printf("tid %d lock succ %d\n", me->tid, me->spin);
/* Should preseve acquire semantic here */
MEMORY_BARRIER();
return 0;
}

int malthusian_mutex_lock(malthusian_mutex_t *impl, malthusian_node_t *me) {
int ret = __malthusian_mutex_lock(impl, me);
assert(ret == 0);
#ifdef COND_VAR
ret = REAL(pthread_mutex_lock)(&impl->posix_lock);
assert(ret == 0);
#endif

return 0;

}

int malthusian_mutex_trylock(malthusian_mutex_t *impl, malthusian_node_t *me) {
malthusian_node_t *tail;

me->next = 0;
me->spin = LOCKED;

/* Try to lock */
tail = __sync_val_compare_and_swap(&impl->tail, 0, me);

/* No one was there - can quickly return */
if (!tail) {

#ifdef COND_VAR
int ret = 0;
while ((ret = REAL(pthread_mutex_trylock)(&impl->posix_lock)) == EBUSY)
CPU_PAUSE();

    assert(ret == 0);

#endif
return 0;
}

return EBUSY;

}

// Helper functions to manage the passive set
static inline malthusian_node_t *
passive_set_pop_back(malthusian_mutex_t *impl) {
malthusian_node_t *elem = impl->passive_set_tail;
if (elem == 0)
return NULL;

impl->passive_set_tail = elem->prev;
if (impl->passive_set_tail == 0)
    impl->passive_set_head = 0;
else
    impl->passive_set_tail->next = 0;

elem->prev = 0;
elem->next = 0;
// printf("pop %d back\n", elem->tid);
return elem;

}

static inline malthusian_node_t *
passive_set_pop_front(malthusian_mutex_t *impl) {
malthusian_node_t *elem = impl->passive_set_head;
if (elem == 0)
return NULL;

impl->passive_set_head = elem->next;
if (impl->passive_set_head == 0)
    impl->passive_set_tail = 0;
else
    impl->passive_set_head->prev = 0;

elem->prev = 0;
elem->next = 0;
// printf("pop %d\n", elem->tid);
return elem;

}

static inline void passive_set_push_front(malthusian_mutex_t *impl,
malthusian_node_t *elem) {
malthusian_node_t *prev_head = impl->passive_set_head;
elem->next = prev_head;
elem->prev = 0;
// printf("push %d\n", elem->tid);
impl->passive_set_head = elem;

if (prev_head != 0) {
    prev_head->prev = elem;
}

if (impl->passive_set_tail == 0)
    impl->passive_set_tail = elem;

}

static void __malthusian_insert_at_head(malthusian_mutex_t *impl,
malthusian_node_t cur_head,
malthusian_node_t new_elem) {
/

* Cur tail is either the current lock holder or a new thread enqueued in
* the meantime (note that several new threads may be enqueued).
* We insert new_elem just behind cur_head and (if any) in front of the
* queue of new threads.
*/
malthusian_node_t cur_tail =
__sync_val_compare_and_swap(&impl->tail, cur_head, new_elem);
if (cur_tail == cur_head) {
cur_head->next = new_elem;
} else {
/

* One or several other threads managed to get inserted in the queue
* before new_elem.
* In this case, we must wait for the first thread to finish
* its insertion and then insert new_elem in front of it.
**/
while (!cur_head->next)
CPU_PAUSE();
new_elem->next = cur_head->next;
cur_head->next = new_elem;
COMPILER_BARRIER();
}
}

static void __malthusian_mutex_unlock(malthusian_mutex_t impl,
malthusian_node_t me) {
/

* "To ensure long-term fairness, the unlock operator periodically
* selects the tail of the excess list T as the successor and then
* grafts T into the main MCS chain immediately after the
* lock-holder's element, passing ownership of the lock to T"
**/
if (xor_random() == 0) {
DEBUG("[%d] Insert T as successor of me\n", cur_thread_id);
malthusian_node_t *elem = passive_set_pop_back(impl);
if (elem != 0) {
// len--;
__malthusian_insert_at_head(impl, me, elem);
// printf("me->tid %d insert head wake %d\n", me->tid, me->next->tid);
waiting_policy_wake(&elem->status);
// sys_futex(&elem->status, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
me->next->spin = 1;
return;
}
}

/* No successor yet? */
if (!me->next) {
    /**
     * "Conversely, at unlock-time if the main queue is empty
     * except for the owner's node, we then extract a node
     * from the head of the passive list, insert it into the
     * queue at the tail and pass ownership to that thread."
     **/
    //  printf("here no next %d ?\n", me->tid);
    DEBUG("[%d - %p] Trying to extract from PS because no waiter\n",
          cur_thread_id, me);
    malthusian_node_t *extract_next = passive_set_pop_front(impl);
    if (extract_next == 0) {
        DEBUG("[%d - %p] No passive thread, old code\n", cur_thread_id, me);
        /* Try to atomically unlock */
        if (__sync_val_compare_and_swap(&impl->tail, me, 0) == me)
            return;

        /* Wait for successor to appear */
        DEBUG("[%d - %p] Wait for successor to appear\n", cur_thread_id,
              me);
        while (!me->next)
            CPU_PAUSE();
        // printf("1 wake tid %d  \n", me->next->tid);
        waiting_policy_wake(&me->next->status);
        me->next->spin = 1;
        return;
    } else {
        DEBUG("[%d - %p] Fetching thread from PS %p\n", cur_thread_id, me,
              extract_next);
        // len--;
        __malthusian_insert_at_head(impl, me, extract_next);
        // printf("2 wake tid %d insert %d \n", me->next->tid, me->tid);
        waiting_policy_wake(&me->next->status);
        me->next->spin = 1;
        return;
    }
}

/**
 * "At unlock-time, if there exists anypa intermediate nodes in the
 * queue between the owner's node and the current tail, then we
 * have a surplus threads in the ACS and we can unlink and excise
 * one of those nodes and transfer it to the head of the passive
 * list where excess "cold" threads reside."
 * Note that we systematically choose the to unlink the thread
 * that is enqueued just behind the current lock holder. *
 **/
if (me->next != impl->tail) {
    DEBUG("[%d - %p] Moving %p to PS\n", cur_thread_id, me, me->next);

    /**
     * It is possible that the successor of the node that we want to unlink
     *it not fully linked yet
     * (i.e., me->next->next is not set).
     * So we wait until the successor has finished its insertion.
     **/
    while (!me->next->next)
        CPU_PAUSE();

    malthusian_node_t *new_next = me->next->next;
    me->next->status = S_PARKING;
    // printf("push %d in passive\n", me->next->tid);
    passive_set_push_front(impl, me->next);
    // len++;
    me->next = new_next;
    COMPILER_BARRIER();
}
// printf("tid %d unlock\n", me->tid);
/* Unlock next one */
// waiting_policy_wake(&me->next->spin);
me->next->spin = 1;

}

void malthusian_mutex_unlock(malthusian_mutex_t *impl, malthusian_node_t *me) {
#ifdef COND_VAR
int ret = REAL(pthread_mutex_unlock)(&impl->posix_lock);
assert(ret == 0);
#endif
__malthusian_mutex_unlock(impl, me);
}

int malthusian_mutex_destroy(malthusian_mutex_t *lock) {
#ifdef COND_VAR
REAL(pthread_mutex_destroy)(&lock->posix_lock);
#endif
free(lock);
lock = NULL;

return 0;

}

int malthusian_cond_init(malthusian_cond_t *cond,
const pthread_condattr_t *attr) {
#ifdef COND_VAR
return REAL(pthread_cond_init)(cond, attr);
#else
fprintf(stderr, "Error cond_var not supported.");
assert(0);
#endif
}

int malthusian_cond_timedwait(malthusian_cond_t *cond, malthusian_mutex_t *lock,
malthusian_node_t *me,
const struct timespec *ts) {
#ifdef COND_VAR
int res;

__malthusian_mutex_unlock(lock, me);
DEBUG("[%d] Sleep cond=%p lock=%p posix_lock=%p\n", cur_thread_id, cond,
      lock, &(lock->posix_lock));
DEBUG_PTHREAD("[%d] Cond posix = %p lock = %p\n", cur_thread_id, cond,
              &lock->posix_lock);

if (ts)
    res = REAL(pthread_cond_timedwait)(cond, &lock->posix_lock, ts);
else
    res = REAL(pthread_cond_wait)(cond, &lock->posix_lock);

if (res != 0 && res != ETIMEDOUT) {
    fprintf(stderr, "Error on cond_{timed,}wait %d\n", res);
    assert(0);
}

int ret = 0;
if ((ret = REAL(pthread_mutex_unlock)(&lock->posix_lock)) != 0) {
    fprintf(stderr, "Error on mutex_unlock %d\n", ret == EPERM);
    assert(0);
}

malthusian_mutex_lock(lock, me);

return res;

#else
fprintf(stderr, "Error cond_var not supported.");
assert(0);
#endif
}

int malthusian_cond_wait(malthusian_cond_t *cond, malthusian_mutex_t *lock,
malthusian_node_t *me) {
return malthusian_cond_timedwait(cond, lock, me, 0);
}

int malthusian_cond_signal(malthusian_cond_t *cond) {
#ifdef COND_VAR
return REAL(pthread_cond_signal)(cond);
#else
fprintf(stderr, "Error cond_var not supported.");
assert(0);
#endif
}

int malthusian_cond_broadcast(malthusian_cond_t *cond) {
#ifdef COND_VAR
DEBUG("[%d] Broadcast cond=%p\n", cur_thread_id, cond);
return REAL(pthread_cond_broadcast)(cond);
#else
fprintf(stderr, "Error cond_var not supported.");
assert(0);
#endif
}

int malthusian_cond_destroy(malthusian_cond_t *cond) {
#ifdef COND_VAR
return REAL(pthread_cond_destroy)(cond);
#else
fprintf(stderr, "Error cond_var not supported.");
assert(0);
#endif
}

void malthusian_thread_start(void) {
}

void malthusian_thread_exit(void) {
}

void malthusian_application_init(void) {
}

void malthusian_application_exit(void) {
}

void malthusian_init_context(lock_mutex_t *UNUSED(impl),
lock_context_t *UNUSED(context),
int UNUSED(number)) {
}

conflict with google flags library

If an application uses google flags,
the call pthread_mutex_init invokes segment fault.

  • Env
    Ubuntu 14.04
    libgflags-dev 2.0-1.1ubuntu1
    gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3)

  • Topology
    #define NUMA_NODES 4
    #define CPU_NUMBER 64
    #define L_CACHE_LINE_SIZE 128
    #define PAGE_SIZE 4096

Following is a simple example:

example.cc

#include <iostream>                                                                      
#include <gflags/gflags.h>                                                               
#include <pthread.h>                                                                     
                                                                                         
DEFINE_bool(verbose, false, "Display program name before message");                      
DEFINE_string(message, "Hello world!", "Message to print");                              
                                                                                         
                                                                                         
int main(int argc, char **argv)                                                          
{                                                                                        
        static pthread_mutex_t mutex;                                                    
        google::ParseCommandLineFlags(&argc, &argv, true);                               
        pthread_mutex_init(&mutex, NULL);                                                
                                                                                         
        if (FLAGS_verbose) std::cout << google::ProgramInvocationShortName() << ": ";    
        std::cout << FLAGS_message << std::endl;                                         
        google::ShutDownCommandLineFlags();                                              
                                                                                         
        pthread_mutex_destroy(&mutex);                                                   
                                                                                         
        return 0;                                                                        
}                                                                                        

  • build
    g++ -o example example.cc -lgflags -lpthread

  • run
    /path/to/litl/lib{lock}_{waiting_policy} ./example
    -> segment fault

  • reason
    call (REAL)(pthread_mutex_init) failed,
    because real_pthread_mutex_init_{lock} is NULL and not correctly linked.

segmentation fault

When I try to run a few tests in the sample directory in upscaledb with a litl script, I get a segment error, but I have no problem running the leveldb db_bench benchmark. How do I fix this problem? Please note that I did not make any code changes while testing.

Errors are as follows:
node1:~/lym/upscaledb$ /home/hpc/litl_ori/litl/libmcs_spinlock.sh ./samples/db1
/home/hpc/litl_ori/litl/libmcs_spinlock.sh: line 33: 151635 Segmentation fault (core dumped)LD_PRELOAD=$LD_PRELOAD:$BASE/lib/libmcs_spinlock.so "$@"

pthread_create interposition not registering with glibc 2.36

Hi,

I am trying to re-use your library to transparently interpose a lock algorithm I am working on.

However, I have an issue with the interposition of pthread_create.
With glibc 2.32 this function is interposed and everything works as intented.
With glibc 2.36 the interposed function of pthread_create is never called. This breaks everything as the per-thread contexts are not initialized and then each thread uses the same context.
The other pthread functions (like pthread_mutex_init for example) are interposed properly.

According to other developers I have been talking to about this issue, glibc 2.34 changed the way pthread is handled (before it was an external shared library, now it is included in libc). This might be related.

Glibc 2.36 is shipped with newer Linux distributions (like Debian 12).
Reproduction steps on a system with glibc 2.36:

  • Enable DEBUG_PTHREAD prints in src/utils.h
  • Compile LiTL
  • Use LiTL on any code with a pthread_create.
    The pthread_create debug line will never be printed as it is not called.

Do you have any idea why this would happen?

Thank you.

benchmark on memcached

I have tried to reproduce performance measurement on memcached. However, I can't more clear details about how you benchmark it. Could you provide more detail such as

  • What tool did you use?
  • What's the config of the benchmarking tool?

I already try utilize memtier_benchmark since it is recommended by official. Here is my benchmarking command.
memtier_benchmark -x 5 -c 50 -t 100 -d 32 --ratio=1:0 --pipeline=1 --key-pattern S:S -P memcache_binary --hide-histogram
Yet, the result after applying linker interposition on memcached is pretty unstable. I would like to have some comment from the author.

Apart from benchmark, I encounter several issues while reading the paper. Please correct me if I misunderstand the content in the paper. According to page 60 Table 4-11, on the memcached-new column, it shows the performance with hmcs lock is poorest. Since pthread has 103 performance gain compared to hmcs, those locks whose performance gain is greater than 103 should probably perform better than original pthread_mutex version such as mcs_stp with 582 performance gain. Nevertheless, the performance isn't like expected and it even has worse performance than pthread_mutex.

Furthermore, I would like to know the testing on pthread_mutex is based on original version with linker interposition in LITL or replace the symbol with libpthreadinterpose_original.sh.

Last but not least, thank you for providing such a great tool to try various lock so easily. It helps me a lot. Please let me know if you need further description to understand my issue. I will provide it ASAP.

static_var pthread_to_lock is 0 after ld_preload segment map

Model name: AMD EPYC 7763 64-Core Processor
Linux localhost.localdomain 4.18.0
glibc-version is 2.28

after i compile litl and try any lock-algorithm, i find that:

2022-08-18 21 03 45

because pthread_to_lock is 0 .

i try to use gdb to find why it becomes '0' after create_clht , at last i find that :

screenshot 2022-08-18 21 06 51

would you mind telling me how to fix this?
maybe i should compile a lower version glibc?

GLIBC 2.35 (and probably earlier) patch to use PLT `pthread_mutex_*` functions in `pthread_cond_*`

If you use this patch + custom glibc (suggest using patchelf so you can stick with the zero-recompile strategy) all code should be interposable without the COND_VAR define. The only thing lost (at least in 2.35+ is that pthread_mutex_destroy may succeed while there are still users of a lock in a pthread_cond_var. This realistically isn't an issue in most correct code.

Also the README doesn't really discuss this but to use LD_PRELOAD interposition your locks initialization must be compatible with PTHREAD_MUTEX_INITIALIZER.

From 6b63d57cd66c224d80ca9a131fbc0a7e3f9dbd37 Mon Sep 17 00:00:00 2001
From: Noah Goldstein <[email protected]>
Date: Thu, 12 May 2022 23:27:35 -0500
Subject: [PATCH v1] Make pthread_cond_var lock usage go through PLT for
 interposition

---
 nptl/Makefile                  |  1 -
 nptl/pthread_cond_wait.c       |  6 +++---
 nptl/pthread_mutex_cond_lock.c | 23 -----------------------
 nptl/pthread_mutex_lock.c      |  7 ++++++-
 4 files changed, 9 insertions(+), 28 deletions(-)
 delete mode 100644 nptl/pthread_mutex_cond_lock.c

diff --git a/nptl/Makefile b/nptl/Makefile
index b585663974..5c779e77eb 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -132,7 +132,6 @@ routines = \
   pthread_keys \
   pthread_kill \
   pthread_kill_other_threads \
-  pthread_mutex_cond_lock \
   pthread_mutex_conf \
   pthread_mutex_consistent \
   pthread_mutex_destroy \
diff --git a/nptl/pthread_cond_wait.c b/nptl/pthread_cond_wait.c
index 20c348a503..9c1aa9e1d0 100644
--- a/nptl/pthread_cond_wait.c
+++ b/nptl/pthread_cond_wait.c
@@ -186,7 +186,7 @@ __condvar_cleanup_waiting (void *arg)
 
   /* XXX If locking the mutex fails, should we just stop execution?  This
      might be better than silently ignoring the error.  */
-  __pthread_mutex_cond_lock (cbuffer->mutex);
+  pthread_mutex_lock (cbuffer->mutex);
 }
 
 /* This condvar implementation guarantees that all calls to signal and
@@ -416,7 +416,7 @@ __pthread_cond_wait_common (pthread_cond_t *cond, pthread_mutex_t *mutex,
      mutex after registering as waiter.
      If releasing the mutex fails, we just cancel our registration as a
      waiter and confirm that we have woken up.  */
-  err = __pthread_mutex_unlock_usercnt (mutex, 0);
+  err = pthread_mutex_unlock (mutex);
   if (__glibc_unlikely (err != 0))
     {
       __condvar_cancel_waiting (cond, seq, g, private);
@@ -604,7 +604,7 @@ __pthread_cond_wait_common (pthread_cond_t *cond, pthread_mutex_t *mutex,
 
   /* Woken up; now re-acquire the mutex.  If this doesn't fail, return RESULT,
      which is set to ETIMEDOUT if a timeout occured, or zero otherwise.  */
-  err = __pthread_mutex_cond_lock (mutex);
+  err = pthread_mutex_lock (mutex);
   /* XXX Abort on errors that are disallowed by POSIX?  */
   return (err != 0) ? err : result;
 }
diff --git a/nptl/pthread_mutex_cond_lock.c b/nptl/pthread_mutex_cond_lock.c
deleted file mode 100644
index f3af514305..0000000000
--- a/nptl/pthread_mutex_cond_lock.c
+++ /dev/null
@@ -1,23 +0,0 @@
-#include <pthreadP.h>
-
-#define LLL_MUTEX_LOCK(mutex) \
-  lll_cond_lock ((mutex)->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex))
-#define LLL_MUTEX_LOCK_OPTIMIZED(mutex) LLL_MUTEX_LOCK (mutex)
-
-/* Not actually elided so far. Needed? */
-#define LLL_MUTEX_LOCK_ELISION(mutex)  \
-  ({ lll_cond_lock ((mutex)->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex)); 0; })
-
-#define LLL_MUTEX_TRYLOCK(mutex) \
-  lll_cond_trylock ((mutex)->__data.__lock)
-#define LLL_MUTEX_TRYLOCK_ELISION(mutex) LLL_MUTEX_TRYLOCK(mutex)
-
-/* We need to assume that there are other threads blocked on the futex.
-   See __pthread_mutex_lock_full for further details.  */
-#define LLL_ROBUST_MUTEX_LOCK_MODIFIER FUTEX_WAITERS
-#define PTHREAD_MUTEX_LOCK  __pthread_mutex_cond_lock
-#define __pthread_mutex_lock_full __pthread_mutex_cond_lock_full
-#define NO_INCR
-#define PTHREAD_MUTEX_VERSIONS 0
-
-#include <nptl/pthread_mutex_lock.c>
diff --git a/nptl/pthread_mutex_lock.c b/nptl/pthread_mutex_lock.c
index d2e652d151..b4adfa2937 100644
--- a/nptl/pthread_mutex_lock.c
+++ b/nptl/pthread_mutex_lock.c
@@ -625,7 +625,12 @@ versioned_symbol (libpthread, ___pthread_mutex_lock, pthread_mutex_lock,
 compat_symbol (libpthread, ___pthread_mutex_lock, __pthread_mutex_lock,
 	       GLIBC_2_0);
 # endif
-#endif /* PTHREAD_MUTEX_VERSIONS */
+#else
+libc_hidden_ver (PTHREAD_MUTEX_LOCK, __pthread_mutex_lock)
+# ifndef SHARED
+strong_alias (___pthread_mutex_lock, __pthread_mutex_lock)
+# endif
+#endif
 
 
 #ifdef NO_INCR
-- 
2.34.1


Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.