On 12/7/22 09:17, Adrian Moreno wrote:
>
>
> On 12/6/22 14:40, Ilya Maximets wrote:
>> On 11/4/22 23:02, Adrian Moreno wrote:
>>> Currently, things like the number of handler and revalidator threads are
>>> calculated based on the number of available CPUs. However, this number
>>> is considered static and only calculated once, hence ignoring events
>>> such as cpus being hotplugged, switched on/off or affinity mask
>>> changing.
>>>
>>> On the other hand, checking the number of available CPUs multiple times
>>> per second seems like an overkill.
>>> Affinity should not change that often and, even if it does, the impact
>>> of destroying and recreating all the threads so often is probably a
>>> price too expensive to pay.
>>>
>>> This patch makes the number of cpus be calculated every time 5 seconds
>>> which seems a reasonable middle point.
>>> It generates an impact in the main loop duration of <1% and a worst-case
>>> scenario impact in throughput of < 5% [1].
>>
>> Thanks, Adrian! See some comments below.
>>
>>>
>>> As a result of these changes (assuming the patch is backported):
>>> - >=2.16: a change in the cpu affinity reflects on the number of threads
>>> in (at most) 5 seconds.
>>> - < 2.16: a change in the cpu affinity will be reflected on
>>> the number of threads the next time there is a call to
>>> bridge_reconfigure() (e.g: on the next DB change), and 5 seconds
>>> have passed.
>>>
>>> The difference in behavior is because on older versions the thread
>>> number calculation was done on bridge reconfiguration while newer
>>> versions moved this logic down to the dpif layer and is run on
>>> dpif->run() stage.
>>> Considering it has not been a huge problem up to today and that the
>>> cpu change would be reflected sooner or later (e.g the user could
>>> force a recalculation with a simple ovs-vsctl command), I think it
>>> might be OK to leave like that.
>>
>> I don't think we should actually backport that change as it changing
>> the behavior of the ovs-vswitchd in a way that may not be expected
>> the the user. It's more like a new feature. So, above paragraphs can
>> be dropped from the commit message.
>>
>> And since it's a new behavior change, it should have a record in the
>> NEWS file. Something like this, maybe:
>>
>> - ovs-vswitchd now detects changes in CPU affinity and adjusts the number
>> of handler and revalidator threads if necessary.
>>
>
> I don't know if, reading how n_revalidator_threads depends on the affinity,
> the user's "expected behavior" is this value not changing when there are
> affinity changes, but it's true that the fact that we'd require a OVSDB
> transaction is an added nuance that's definitely not expected on old versions.
>
> If we're not going to backport it, do you think we should add a config knob
> to modify the cpu affinity check timeout (currently 5s)? I've restrained
> myself from adding yet-another-config-value that has some potential
> associated damage and little benefit but I'd like to know your opinion on
> this.
I'd say we don't need a knob at the moment. We can add it later, if required.
Maybe bump the timeout to 10 seconds though? (Just a random number that's
seems a bit more comfortable to me, possibly because it matches the default
flow timout).
>
>>>
>>> [1] Tested in the worst-case scenario of disabling the kernel cache
>>> (other_config:flow-size=0), modifying ovs-vswithd's affinity so the
>>> number of handlers go up and down every 5 seconds and calculated the
>>> difference in netperf's ops/sec.
>>>
>>> Fixes: be15ec48d766 ("lib: Use a more accurate value for CPU count
>>> (sched_getaffinity).")
>>
>> And this tag is not really needed.
>>
>>> Cc: [email protected]
>>> Signed-off-by: Adrian Moreno <[email protected]>
>>> ---
>>> lib/ovs-thread.c | 61 +++++++++++++++++++++++++++++-------------------
>>> 1 file changed, 37 insertions(+), 24 deletions(-)
>>>
>>> diff --git a/lib/ovs-thread.c b/lib/ovs-thread.c
>>> index 78ed3e970..d1deb9c52 100644
>>> --- a/lib/ovs-thread.c
>>> +++ b/lib/ovs-thread.c
>>> @@ -31,6 +31,7 @@
>>> #include "openvswitch/poll-loop.h"
>>> #include "seq.h"
>>> #include "socket-util.h"
>>> +#include "timeval.h"
>>> #include "util.h"
>>> #ifdef __CHECKER__
>>> @@ -627,42 +628,54 @@ ovs_thread_stats_next_bucket(const struct
>>> ovsthread_stats *stats, size_t i)
>>> }
>>>
>>> -/* Returns the total number of cores available to this process, or 0 if the
>>> - * number cannot be determined. */
>>> -int
>>> -count_cpu_cores(void)
>>> +static int
>>> +count_cpu_cores__(void)
>>> {
>>> - static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
>>> - static long int n_cores;
>>> + long int n_cores;
>>> - if (ovsthread_once_start(&once)) {
>>> #ifndef _WIN32
>>> - n_cores = sysconf(_SC_NPROCESSORS_ONLN);
>>> + n_cores = sysconf(_SC_NPROCESSORS_ONLN);
>>> +#else
>>> + SYSTEM_INFO sysinfo;
>>> + GetSystemInfo(&sysinfo);
>>> + n_cores = sysinfo.dwNumberOfProcessors;
>>> +#endif
>>> #ifdef __linux__
>>> - if (n_cores > 0) {
>>> - cpu_set_t *set = CPU_ALLOC(n_cores);
>>> + if (n_cores > 0) {
>>> + cpu_set_t *set = CPU_ALLOC(n_cores);
>>> - if (set) {
>>> - size_t size = CPU_ALLOC_SIZE(n_cores);
>>> + if (set) {
>>> + size_t size = CPU_ALLOC_SIZE(n_cores);
>>> - if (!sched_getaffinity(0, size, set)) {
>>> - n_cores = CPU_COUNT_S(size, set);
>>> - }
>>> - CPU_FREE(set);
>>> + if (!sched_getaffinity(0, size, set)) {
>>> + n_cores = CPU_COUNT_S(size, set);
>>> }
>>> + CPU_FREE(set);
>>> }
>>> -#endif
>>> -#else
>>> - SYSTEM_INFO sysinfo;
>>> - GetSystemInfo(&sysinfo);
>>> - n_cores = sysinfo.dwNumberOfProcessors;
>>> -#endif
>>> - ovsthread_once_done(&once);
>>> }
>>> -
>>> +#endif
>>> return n_cores > 0 ? n_cores : 0;
>>> }
>>> +/* It's unlikely that the available cpus change several times per second
>>> and
>>> + * even if it does, it's not needed (or desired) to react to such changes
>>> so
>>> + * quickly.*/
>>> +#define COUNT_CPU_UPDATE_TIME_MS 5000
>>> +/* Returns the current total number of cores available to this process, or >>> 0
>>> + * if the number cannot be determined.
>>> + * It is assumed that this function is only called from the main thread.*/
>>
>> There is a missing space at the end. But, more importantly, this
>> assumption is not correct. The function is called from the system
>> stats thread, i.e. system_stats_thread_func(). So, updates inside
>> should be guarded by some form of a mutex.
>>
>>> +int count_cpu_cores(void) {
>>
>> Braces should be on a new line.
>>
>>> + static long long int last_updated = 0;
>>> + long long int now = time_msec();
>>> + static int cpu_cores;
>>> +
>>> + if (now - last_updated >= COUNT_CPU_UPDATE_TIME_MS) {
>>> + last_updated = now;
>>> + cpu_cores = count_cpu_cores__();
>>> + }
>>> + return cpu_cores;
>>> +}
>>> +
>>> /* Returns the total number of cores on the system, or 0 if the
>>> * number cannot be determined. */
>>> int
>>
>> Bets regards, Ilya Maximets.
>>
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev