| /* |
| * Copyright (c) 2019 Arm Limited. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include <vppinfra/mem.h> |
| #include <vppinfra/cache.h> |
| #include <vppinfra/lock.h> |
| #include <pthread.h> |
| #include <vppinfra/format.h> |
| #include <vppinfra/error.h> |
| #include <vppinfra/time.h> |
| #include <sched.h> |
| #include <vppinfra/atomics.h> |
| |
| static u32 all_threads_online = 0; |
| |
| typedef struct |
| { |
| uword threads_per_core; |
| uword cpu_mask_read; |
| uword read_cores; |
| uword cpu_mask_write; |
| uword write_cores; |
| uword increment_per_thread; |
| clib_rwlock_t rwlock; |
| uword shared_count; |
| uword iterations; |
| } rwlock_test_main_t; |
| |
| void * |
| write_shared_counter (void *arg) |
| { |
| f64 *time = vec_new (f64, 1); |
| *time = 0; |
| rwlock_test_main_t *rtm = arg; |
| |
| /* Wait for all threads to be created */ |
| while (!clib_atomic_load_acq_n (&all_threads_online)); |
| |
| f64 start = clib_cpu_time_now (); |
| for (uword i = 0; i < rtm->increment_per_thread; i++) |
| { |
| clib_rwlock_writer_lock (&rtm->rwlock); |
| rtm->shared_count++; |
| clib_rwlock_writer_unlock (&rtm->rwlock); |
| } |
| *time = clib_cpu_time_now () - start; |
| return time; |
| } |
| |
| void * |
| read_shared_counter (void *arg) |
| { |
| f64 *time = vec_new (f64, 1); |
| *time = 0; |
| rwlock_test_main_t *rtm = arg; |
| uword cnt_cpy = 0, exp = rtm->increment_per_thread * rtm->write_cores * |
| rtm->threads_per_core; |
| |
| /* Wait for all threads to be created */ |
| while (!clib_atomic_load_acq_n (&all_threads_online)); |
| |
| f64 start = clib_cpu_time_now (); |
| while (cnt_cpy < exp) |
| { |
| clib_rwlock_reader_lock (&rtm->rwlock); |
| cnt_cpy = rtm->shared_count; |
| clib_rwlock_reader_unlock (&rtm->rwlock); |
| } |
| *time = clib_cpu_time_now () - start; |
| return time; |
| } |
| |
| unsigned |
| test_rwlock (rwlock_test_main_t * rtm, f64 * elapse_time) |
| { |
| int error = 0, total_threads = (rtm->read_cores + rtm->write_cores) |
| * rtm->threads_per_core; |
| pthread_t pthread[total_threads]; |
| |
| cpu_set_t cpuset; |
| unsigned cores_set = 0, cpu_id = 0; |
| |
| /* Spawn reader (consumer) threads */ |
| for (unsigned cpu_mask = rtm->cpu_mask_read; cpu_mask; cpu_mask >>= 1) |
| { |
| if (!(cpu_mask & 1)) |
| { |
| cpu_id++; |
| continue; |
| } |
| |
| CPU_ZERO (&cpuset); |
| CPU_SET (cpu_id, &cpuset); |
| for (uword t_num = 0; t_num < rtm->threads_per_core; t_num++) |
| { |
| uword t_index = cores_set * rtm->threads_per_core + t_num; |
| if ((error = pthread_create (&pthread[t_index], NULL, |
| &read_shared_counter, rtm))) |
| clib_unix_warning ("pthread_create failed with %d", error); |
| |
| if ((error = pthread_setaffinity_np (pthread[t_index], |
| sizeof (cpu_set_t), &cpuset))) |
| clib_unix_warning ("pthread_set_affinity_np failed with %d", |
| error); |
| } |
| cores_set++; |
| cpu_id++; |
| } |
| |
| /* Spawn writer (producer) threads */ |
| cpu_id = 0; |
| for (unsigned cpu_mask = rtm->cpu_mask_write; cpu_mask; cpu_mask >>= 1) |
| { |
| if (!(cpu_mask & 1)) |
| { |
| cpu_id++; |
| continue; |
| } |
| |
| CPU_ZERO (&cpuset); |
| CPU_SET (cpu_id, &cpuset); |
| for (uword t_num = 0; t_num < rtm->threads_per_core; t_num++) |
| { |
| uword t_index = cores_set * rtm->threads_per_core + t_num; |
| if ((error = pthread_create (&pthread[t_index], NULL, |
| &write_shared_counter, rtm))) |
| clib_unix_warning ("pthread_create failed with %d", error); |
| |
| if ((error = pthread_setaffinity_np (pthread[t_index], |
| sizeof (cpu_set_t), &cpuset))) |
| clib_unix_warning ("pthread_set_affinity_np failed with %d", |
| error); |
| } |
| cores_set++; |
| cpu_id++; |
| } |
| |
| /* Launch all threads */ |
| clib_atomic_store_rel_n (&all_threads_online, 1); |
| |
| for (uword thread_num = 0; thread_num < total_threads; thread_num++) |
| { |
| f64 *time; |
| if ((error = pthread_join (pthread[thread_num], (void *) &time))) |
| clib_unix_warning ("pthread_join failed with %d", error); |
| *elapse_time += *time; |
| vec_free (time); |
| } |
| |
| fformat (stdout, "Time elapsed: %.4e\n", *elapse_time); |
| return rtm->shared_count; |
| } |
| |
| uword |
| num_cores_in_cpu_mask (uword mask) |
| { |
| uword num_cores = 0; |
| for (uword cpu_mask = mask; cpu_mask; cpu_mask >>= 1) |
| num_cores += (cpu_mask & 1); |
| return num_cores; |
| } |
| |
| int |
| test_rwlock_main (unformat_input_t * i) |
| { |
| rwlock_test_main_t _rtm, *rtm = &_rtm; |
| clib_memset (rtm, 0, sizeof (rwlock_test_main_t)); |
| |
| while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) |
| { |
| if (0 == unformat (i, "threads/core %d", &rtm->threads_per_core) |
| && 0 == unformat (i, "cpu_mask_read %x", &rtm->cpu_mask_read) |
| && 0 == unformat (i, "cpu_mask_write %x", &rtm->cpu_mask_write) |
| && 0 == unformat (i, "increment %d", &rtm->increment_per_thread) |
| && 0 == unformat (i, "iterations %d", &rtm->iterations)) |
| { |
| clib_unix_warning ("unknown input '%U'", format_unformat_error, i); |
| return 1; |
| } |
| } |
| |
| rtm->read_cores = num_cores_in_cpu_mask (rtm->cpu_mask_read); |
| rtm->write_cores = num_cores_in_cpu_mask (rtm->cpu_mask_write); |
| |
| uword total_increment = rtm->threads_per_core * rtm->write_cores * |
| rtm->increment_per_thread; |
| |
| clib_rwlock_init (&rtm->rwlock); |
| |
| f64 average_time = 0; |
| for (uword trial = 0; trial < rtm->iterations; trial++) |
| { |
| rtm->shared_count = 0; |
| f64 elapse_time = 0; |
| if (test_rwlock (rtm, &elapse_time) != total_increment) |
| { |
| clib_rwlock_free (&rtm->rwlock); |
| fformat (stdout, "FAILED: expected count: %d, actual count: %d\n", |
| total_increment, rtm->shared_count); |
| return 1; |
| } |
| fformat (stdout, "Trial %d SUCCESS: %d = %d\n", |
| trial, rtm->shared_count, total_increment); |
| average_time = (average_time * trial + elapse_time) / (trial + 1); |
| fformat (stdout, "Average lock/unlock cycles: %.4e\n", average_time); |
| } |
| clib_rwlock_free (&rtm->rwlock); |
| return 0; |
| } |
| |
| #ifdef CLIB_UNIX |
| /** Launches a number of writer threads to simultaneously increment a global |
| counter and a number of reader threads to continuously poll the counter, |
| and records timestamps for rwlock performance benchmarking |
| |
| @param "threads/core [# threads/core]" - number of threads per core |
| @param "cpu_mask_read [cpu_mask]" - reader thread cpu string e.g. input |
| ff sets cpus 0 - 7 |
| @param "cpu_mask_write [cpu_mask]" - writer thread cpu string |
| @param "increment [# increments]" - number of increments per writer thread |
| @param "iterations [# iterations]" - number of iterations |
| @returns exit code |
| */ |
| int |
| main (int argc, char *argv[]) |
| { |
| unformat_input_t i; |
| i32 ret; |
| clib_time_t time; |
| |
| clib_mem_init (0, 3ULL << 30); |
| clib_time_init (&time); |
| |
| unformat_init_command_line (&i, argv); |
| ret = test_rwlock_main (&i); |
| unformat_free (&i); |
| |
| return ret; |
| } |
| #endif /* CLIB_UNIX */ |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |