redis-py
                                
                                 redis-py copied to clipboard
                                
                                    redis-py copied to clipboard
                            
                            
                            
                        Debug fstring bottleneck in sending
In the file cluster.py there is a line that logs the args as debug using a fstring. This is a significant bottleneck when sending large messages even when logging is disabled since the fstring is always evaluated. Please could it be changed as follows:
diff cluster.py cluster.py -u
--- cluster.py 2022-04-12 09:47:46.545790433 +0000
+++ cluster.py 2022-04-12 09:51:55.789286947 +0000
@@ -900,7 +900,7 @@
         node = self.nodes_manager.get_node_from_slot(
             slot, self.read_from_replicas and command in READ_COMMANDS
         )
- 
log.debug(f"Target for {args}: slot {slot}")
- 
log.debug("Target for {%s}: slot {%s}",args,slot) return [node]def _should_reinitialized(self): 
@dvora-h mind taking a look? This looks like a bug we picked up in the cluster PR and should not be present.
@chayim @dvora-h can i take this issue and fix it all over the code? I'm happy to 😊
Here is explanation about why %-string is better:
https://blog.pilosus.org/posts/2020/01/24/python-f-strings-in-logging/
There's been a lot of discussion on f-strings being better than %s formatters recently (and hence the switch to that in redis-py).
If we can demonstrate this is true, then we should also remove the string checker for CI (cc @dvora-h )
f-strings are only a bottleneck for logging because %s substitutions are deferred & possibly skipped, like in the case of debug logs in cluster code, which were recently removed in #2238.
@chayim I'm happy to demonstrate, do you mean in general demonstration, or in redis's use case?
If you mean in redis, do you have a set of performance tests which I can use?
@alongouldman We don't have perf tests here in the repo that are viable - though we keep starting and stopping that.
I mean the redis use case. But frankly, if it's generically pythonic (as in how list comprehensions beat out pure for loops), that's fine.
Hi @chayim
This is the test I run:
import logging
import timeit
def short_f_string():
	name = "world"
	logging.debug(f"hello {name}")
def long_f_string():
	name = """
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	"""
	logging.debug(f"hello {name}")
def multiple_f_string():
	name = "world"
	other = "how are you today"
	num = 4
	logging.debug(f"hello {name}, {other}? {num} | hello {name}, {other}? {num} | hello {name}, {other}? {num} | hello {name}, {other}? {num}")
def short_string_format():
	name = "world"
	logging.debug("hello %s", name)
def long_string_format():
	name = """
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	looooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnngggggggggggggggggg  massage wow this is so longg
	"""
	logging.debug("hello %s", name)
def multiple_string_format():
	name = "world"
	other = "how are you today"
	num = 4
	logging.debug(
		"hello %s, %s? %d | hello %s, %s? %d | hello %s, %s? %d | hello %s, %s? %d",
		name, other, num,
		name, other, num,
		name, other, num,
		name, other, num,
	)
if __name__ == "__main__":
	amount = 10000000
	print("string_format")
	print("short_string_format: ", timeit.timeit(short_string_format, number=amount))
	print("long_string_format: ", timeit.timeit(long_string_format, number=amount))
	print("multiple_string_format: ", timeit.timeit(multiple_string_format, number=amount))
	print("f-string")
	print("short_f_string: ", timeit.timeit(short_f_string, number=amount))
	print("long_f_string: ", timeit.timeit(long_f_string, number=amount))
	print("multiple_f_string: ", timeit.timeit(multiple_f_string, number=amount))
These are the results:
➜  /Users/alon/Library/Application Support/JetBrains/PyCharm2021.3/scratches python scratch_22.py
f-string
short_f_string:  7.538512164999999
long_f_string:  8.713512263999998
multiple_f_string:  13.378046865999998
string_format
short_string_format:  7.461573618999996
long_string_format:  7.435051018999999
multiple_string_format:  8.826595583
➜  /Users/alon/Library/Application Support/JetBrains/PyCharm2021.3/scratches python scratch_22.py
string_format
short_string_format:  7.590091192999999
long_string_format:  7.533264764000001
multiple_string_format:  8.920953102
f-string
short_f_string:  7.827805414
long_f_string:  9.243438682
multiple_f_string:  13.923798607999998
➜  /Users/alon/Library/Application Support/JetBrains/PyCharm2021.3/scratches python scratch_22.py
string_format
short_string_format:  8.702303225
long_string_format:  8.292103442999998
multiple_string_format:  9.268010628000003
f-string
short_f_string:  8.124534014999998
long_f_string:  9.488321374000002
multiple_f_string:  14.037382780999998
➜  /Users/alon/Library/Application Support/JetBrains/PyCharm2021.3/scratches python scratch_22.py
f-string
short_f_string:  8.382262702
long_f_string:  9.412909211000002
multiple_f_string:  13.798953093000002
string_format
short_string_format:  7.728673025999999
long_string_format:  7.664428079000004
multiple_string_format:  8.991047841000004
(I tried to change the order of the function calls, just to make sure that its nothing to do with the order for some reason)
Looks like in a single short string format, there is no much difference... but either long text, or multiple arguments, %-format is faster.
What do you think? Should I change that around redis-py's codebase?
This issue is marked stale. It will be closed in 30 days if it is not updated.