diff mbox series

[1/2] io_uring: Move from hlist to io_wq_work_node

Message ID 20230221135721.3230763-1-leitao@debian.org (mailing list archive)
State New
Headers show
Series [1/2] io_uring: Move from hlist to io_wq_work_node | expand

Commit Message

Breno Leitao Feb. 21, 2023, 1:57 p.m. UTC
Having cache entries linked using the hlist format brings no benefit, and
also requires an unnecessary extra pointer address per cache entry.

Use the internal io_wq_work_node single-linked list for the internal
alloc caches (async_msghdr and async_poll)

This is required to be able to use KASAN on cache entries, since we do
not need to touch unused (and poisoned) cache entries when adding more
entries to the list.

Suggested-by: Pavel Begunkov <asml.silence@gmail.com>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
 include/linux/io_uring_types.h |  2 +-
 io_uring/alloc_cache.h         | 27 +++++++++++++++------------
 2 files changed, 16 insertions(+), 13 deletions(-)

Comments

Pavel Begunkov Feb. 21, 2023, 5:45 p.m. UTC | #1
On 2/21/23 13:57, Breno Leitao wrote:
> Having cache entries linked using the hlist format brings no benefit, and
> also requires an unnecessary extra pointer address per cache entry.
> 
> Use the internal io_wq_work_node single-linked list for the internal
> alloc caches (async_msghdr and async_poll)
> 
> This is required to be able to use KASAN on cache entries, since we do
> not need to touch unused (and poisoned) cache entries when adding more
> entries to the list.

Looks good, a few nits

> 
> Suggested-by: Pavel Begunkov <asml.silence@gmail.com>
> Signed-off-by: Breno Leitao <leitao@debian.org>
> ---
>   include/linux/io_uring_types.h |  2 +-
>   io_uring/alloc_cache.h         | 27 +++++++++++++++------------
>   2 files changed, 16 insertions(+), 13 deletions(-)
> 
> diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h
> index 0efe4d784358..efa66b6c32c9 100644
> --- a/include/linux/io_uring_types.h
> +++ b/include/linux/io_uring_types.h
> @@ -188,7 +188,7 @@ struct io_ev_fd {
>   };
>   
[...]
> -	if (!hlist_empty(&cache->list)) {
> -		struct hlist_node *node = cache->list.first;
> -
> -		hlist_del(node);
> -		return container_of(node, struct io_cache_entry, node);
> +	struct io_wq_work_node *node;
> +	struct io_cache_entry *entry;
> +
> +	if (cache->list.next) {
> +		node = cache->list.next;
> +		entry = container_of(node, struct io_cache_entry, node);

I'd prefer to get rid of the node var, it'd be a bit cleaner
than keeping two pointers to the same chunk.

entry = container_of(node, struct io_cache_entry,
                      cache->list.next);

> +		cache->list.next = node->next;
> +		return entry;
>   	}
>   
>   	return NULL;
> @@ -35,19 +38,19 @@ static inline struct io_cache_entry *io_alloc_cache_get(struct io_alloc_cache *c
>   
>   static inline void io_alloc_cache_init(struct io_alloc_cache *cache)
>   {
> -	INIT_HLIST_HEAD(&cache->list);
> +	cache->list.next = NULL;
>   	cache->nr_cached = 0;
>   }
>   
>   static inline void io_alloc_cache_free(struct io_alloc_cache *cache,
>   					void (*free)(struct io_cache_entry *))
>   {
> -	while (!hlist_empty(&cache->list)) {
> -		struct hlist_node *node = cache->list.first;
> +	struct io_cache_entry *entry;
>   
> -		hlist_del(node);
> -		free(container_of(node, struct io_cache_entry, node));
> +	while ((entry = io_alloc_cache_get(cache))) {
> +		free(entry);

We don't need brackets here. Personally, I don't have anything
against assignments in if, but it's probably better to avoid them,
or there will be a patch in a couple of months based on a random
code analysis report as happened many times before.

while (1) {
	struct io_cache_entry *entry = get();

	if (!entry)
		break;
	free(entry);
}
Breno Leitao Feb. 21, 2023, 6:38 p.m. UTC | #2
On 21/02/2023 17:45, Pavel Begunkov wrote:
> On 2/21/23 13:57, Breno Leitao wrote:
>> Having cache entries linked using the hlist format brings no benefit, and
>> also requires an unnecessary extra pointer address per cache entry.
>>
>> Use the internal io_wq_work_node single-linked list for the internal
>> alloc caches (async_msghdr and async_poll)
>>
>> This is required to be able to use KASAN on cache entries, since we do
>> not need to touch unused (and poisoned) cache entries when adding more
>> entries to the list.
> 
> Looks good, a few nits
> 
>>
>> Suggested-by: Pavel Begunkov <asml.silence@gmail.com>
>> Signed-off-by: Breno Leitao <leitao@debian.org>
>> ---
>>   include/linux/io_uring_types.h |  2 +-
>>   io_uring/alloc_cache.h         | 27 +++++++++++++++------------
>>   2 files changed, 16 insertions(+), 13 deletions(-)
>>
>> diff --git a/include/linux/io_uring_types.h
>> b/include/linux/io_uring_types.h
>> index 0efe4d784358..efa66b6c32c9 100644
>> --- a/include/linux/io_uring_types.h
>> +++ b/include/linux/io_uring_types.h
>> @@ -188,7 +188,7 @@ struct io_ev_fd {
>>   };
>>   
> [...]
>> -    if (!hlist_empty(&cache->list)) {
>> -        struct hlist_node *node = cache->list.first;
>> -
>> -        hlist_del(node);
>> -        return container_of(node, struct io_cache_entry, node);
>> +    struct io_wq_work_node *node;
>> +    struct io_cache_entry *entry;
>> +
>> +    if (cache->list.next) {
>> +        node = cache->list.next;
>> +        entry = container_of(node, struct io_cache_entry, node);
> 
> I'd prefer to get rid of the node var, it'd be a bit cleaner
> than keeping two pointers to the same chunk.
> 
> entry = container_of(node, struct io_cache_entry,
>                      cache->list.next);
> 
>> +        cache->list.next = node->next;
>> +        return entry;
>>       }
>>         return NULL;
>> @@ -35,19 +38,19 @@ static inline struct io_cache_entry
>> *io_alloc_cache_get(struct io_alloc_cache *c
>>     static inline void io_alloc_cache_init(struct io_alloc_cache *cache)
>>   {
>> -    INIT_HLIST_HEAD(&cache->list);
>> +    cache->list.next = NULL;
>>       cache->nr_cached = 0;
>>   }
>>     static inline void io_alloc_cache_free(struct io_alloc_cache *cache,
>>                       void (*free)(struct io_cache_entry *))
>>   {
>> -    while (!hlist_empty(&cache->list)) {
>> -        struct hlist_node *node = cache->list.first;
>> +    struct io_cache_entry *entry;
>>   -        hlist_del(node);
>> -        free(container_of(node, struct io_cache_entry, node));
>> +    while ((entry = io_alloc_cache_get(cache))) {
>> +        free(entry);
> 
> We don't need brackets here.

The extra brackets are required if we are assignments in if, otherwise
the compiler raises a warning (bugprone-assignment-in-if-condition)

> Personally, I don't have anything
> against assignments in if, but it's probably better to avoid them

Sure. I will remove the assignents in "if" part and maybe replicate what
we have
in io_alloc_cache_get(). Something as:
       if (cache->list.next) {
               node = cache->list.next;

Thanks for the review!
Pavel Begunkov Feb. 21, 2023, 6:43 p.m. UTC | #3
On 2/21/23 18:38, Breno Leitao wrote:
> On 21/02/2023 17:45, Pavel Begunkov wrote:
>> On 2/21/23 13:57, Breno Leitao wrote:
>>> Having cache entries linked using the hlist format brings no benefit, and
>>> also requires an unnecessary extra pointer address per cache entry.
>>>
>>> Use the internal io_wq_work_node single-linked list for the internal
>>> alloc caches (async_msghdr and async_poll)
>>>
>>> This is required to be able to use KASAN on cache entries, since we do
>>> not need to touch unused (and poisoned) cache entries when adding more
>>> entries to the list.
>>
>> Looks good, a few nits
>>
>>>
>>> Suggested-by: Pavel Begunkov <asml.silence@gmail.com>
>>> Signed-off-by: Breno Leitao <leitao@debian.org>
>>> ---
>>>    include/linux/io_uring_types.h |  2 +-
>>>    io_uring/alloc_cache.h         | 27 +++++++++++++++------------
>>>    2 files changed, 16 insertions(+), 13 deletions(-)
>>>
>>> diff --git a/include/linux/io_uring_types.h
>>> b/include/linux/io_uring_types.h
>>> index 0efe4d784358..efa66b6c32c9 100644
>>> --- a/include/linux/io_uring_types.h
>>> +++ b/include/linux/io_uring_types.h
>>> @@ -188,7 +188,7 @@ struct io_ev_fd {
>>>    };
>>>    
>> [...]
>>> -    if (!hlist_empty(&cache->list)) {
>>> -        struct hlist_node *node = cache->list.first;
>>> -
>>> -        hlist_del(node);
>>> -        return container_of(node, struct io_cache_entry, node);
>>> +    struct io_wq_work_node *node;
>>> +    struct io_cache_entry *entry;
>>> +
>>> +    if (cache->list.next) {
>>> +        node = cache->list.next;
>>> +        entry = container_of(node, struct io_cache_entry, node);
>>
>> I'd prefer to get rid of the node var, it'd be a bit cleaner
>> than keeping two pointers to the same chunk.
>>
>> entry = container_of(node, struct io_cache_entry,
>>                       cache->list.next);
>>
>>> +        cache->list.next = node->next;
>>> +        return entry;
>>>        }
>>>          return NULL;
>>> @@ -35,19 +38,19 @@ static inline struct io_cache_entry
>>> *io_alloc_cache_get(struct io_alloc_cache *c
>>>      static inline void io_alloc_cache_init(struct io_alloc_cache *cache)
>>>    {
>>> -    INIT_HLIST_HEAD(&cache->list);
>>> +    cache->list.next = NULL;
>>>        cache->nr_cached = 0;
>>>    }
>>>      static inline void io_alloc_cache_free(struct io_alloc_cache *cache,
>>>                        void (*free)(struct io_cache_entry *))
>>>    {
>>> -    while (!hlist_empty(&cache->list)) {
>>> -        struct hlist_node *node = cache->list.first;
>>> +    struct io_cache_entry *entry;
>>>    -        hlist_del(node);
>>> -        free(container_of(node, struct io_cache_entry, node));
>>> +    while ((entry = io_alloc_cache_get(cache))) {
>>> +        free(entry);
>>
>> We don't need brackets here.
> 
> The extra brackets are required if we are assignments in if, otherwise
> the compiler raises a warning (bugprone-assignment-in-if-condition)

I mean braces / curly brackets.
>> Personally, I don't have anything
>> against assignments in if, but it's probably better to avoid them
> 
> Sure. I will remove the assignents in "if" part and maybe replicate what
> we have
> in io_alloc_cache_get(). Something as:
>         if (cache->list.next) {
>                 node = cache->list.next;
> 
> Thanks for the review!
Jens Axboe Feb. 21, 2023, 11:53 p.m. UTC | #4
On 2/21/23 11:38?AM, Breno Leitao wrote:
> Sure. I will remove the assignents in "if" part and maybe replicate what
> we have
> in io_alloc_cache_get(). Something as:
>        if (cache->list.next) {
>                node = cache->list.next;
> 
> Thanks for the review!

Pavel is right in that we generally discourage assignments in if
statements etc. For the above, the usual approach would be:

node = cache->list.next;
if (node) {
	...
}
diff mbox series

Patch

diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h
index 0efe4d784358..efa66b6c32c9 100644
--- a/include/linux/io_uring_types.h
+++ b/include/linux/io_uring_types.h
@@ -188,7 +188,7 @@  struct io_ev_fd {
 };
 
 struct io_alloc_cache {
-	struct hlist_head	list;
+	struct io_wq_work_node	list;
 	unsigned int		nr_cached;
 };
 
diff --git a/io_uring/alloc_cache.h b/io_uring/alloc_cache.h
index 729793ae9712..0d9ff9402a37 100644
--- a/io_uring/alloc_cache.h
+++ b/io_uring/alloc_cache.h
@@ -7,7 +7,7 @@ 
 #define IO_ALLOC_CACHE_MAX	512
 
 struct io_cache_entry {
-	struct hlist_node	node;
+	struct io_wq_work_node node;
 };
 
 static inline bool io_alloc_cache_put(struct io_alloc_cache *cache,
@@ -15,7 +15,7 @@  static inline bool io_alloc_cache_put(struct io_alloc_cache *cache,
 {
 	if (cache->nr_cached < IO_ALLOC_CACHE_MAX) {
 		cache->nr_cached++;
-		hlist_add_head(&entry->node, &cache->list);
+		wq_stack_add_head(&entry->node, &cache->list);
 		return true;
 	}
 	return false;
@@ -23,11 +23,14 @@  static inline bool io_alloc_cache_put(struct io_alloc_cache *cache,
 
 static inline struct io_cache_entry *io_alloc_cache_get(struct io_alloc_cache *cache)
 {
-	if (!hlist_empty(&cache->list)) {
-		struct hlist_node *node = cache->list.first;
-
-		hlist_del(node);
-		return container_of(node, struct io_cache_entry, node);
+	struct io_wq_work_node *node;
+	struct io_cache_entry *entry;
+
+	if (cache->list.next) {
+		node = cache->list.next;
+		entry = container_of(node, struct io_cache_entry, node);
+		cache->list.next = node->next;
+		return entry;
 	}
 
 	return NULL;
@@ -35,19 +38,19 @@  static inline struct io_cache_entry *io_alloc_cache_get(struct io_alloc_cache *c
 
 static inline void io_alloc_cache_init(struct io_alloc_cache *cache)
 {
-	INIT_HLIST_HEAD(&cache->list);
+	cache->list.next = NULL;
 	cache->nr_cached = 0;
 }
 
 static inline void io_alloc_cache_free(struct io_alloc_cache *cache,
 					void (*free)(struct io_cache_entry *))
 {
-	while (!hlist_empty(&cache->list)) {
-		struct hlist_node *node = cache->list.first;
+	struct io_cache_entry *entry;
 
-		hlist_del(node);
-		free(container_of(node, struct io_cache_entry, node));
+	while ((entry = io_alloc_cache_get(cache))) {
+		free(entry);
 	}
+
 	cache->nr_cached = 0;
 }
 #endif