Performance for simple code that converts a RGB tuple to hex stringCan I improve this code for readability and/or performance?Performance of speech enhancement code for Android appGrabs 10 bytes and converts it into a hex formatted stringLightweight LED libraryAlgorithm that receives a dictionary, converts it to a GET string, and is optimized for big dataWIP library that will encrypt some text for the purpose of obfuscating my codeSimple python code takes command line argument for file location and tokenizes textImproving performance of a subroutine that checks for a vacancy in a latticeperformance test the code for finding time taken to fill the disk spaceSimple tkinter application that choose a random string
Pawns pushes in the center
Is there a guide/reference for character hairstyle possible in D&D Forgotten Realms universe?
Is Fairphone violating the GPL with its newest Fairphone 3?
How do I find more throat sprays?
80s/90s sitcom with a girl who could stop time and spoke to her dad through a "gem thingy"
United States Towns/States Traversing by Name Puzzle
What does "two ells within the selvages" mean in the Magna Carta?
Could rakshasas be detected by paladin’s Divine Sense?
How to help a male-presenting person shop for women's clothes?
Why would anyone choose not use the lowlatency kernel?
Are shells allowed to ignore NUL bytes in scripts?
Storing info in JWT payload
How much would we learn from observing an FTL starship fly by?
How do you all work out the relative costs of spells?
Will a falling rod stay in contact with the frictionless floor?
What's the trick to shaking the berry trees?
Apollo CM heat shield burnt pattern around RCS thrusters
“You are not paid to think, but to do X” is always wrong in the workplace?
Bitcoin protocol and Wireshark
Feeling burned-out in PhD. program and thinking about dropping out
80’s or earlier short fantasy about very “sweet” neighbours
Chess Tournaments without Studying Theory?
Why does Sisko say Kirk's Enterprise is the first one?
Why is it recommended to combine WordPress and Godaddy for hosting?
Performance for simple code that converts a RGB tuple to hex string
Can I improve this code for readability and/or performance?Performance of speech enhancement code for Android appGrabs 10 bytes and converts it into a hex formatted stringLightweight LED libraryAlgorithm that receives a dictionary, converts it to a GET string, and is optimized for big dataWIP library that will encrypt some text for the purpose of obfuscating my codeSimple python code takes command line argument for file location and tokenizes textImproving performance of a subroutine that checks for a vacancy in a latticeperformance test the code for finding time taken to fill the disk spaceSimple tkinter application that choose a random string
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty
margin-bottom:0;
$begingroup$
I'm rewriting a full color Mandelbrot Set explorer in Python using tkinter. For it, I need to be able to convert a Tuple[int, int, int]
into a hex string in the form #123456
. Here are example uses of the two variants that I came up with:
>>>rgb_to_hex(123, 45, 6)
'#7b2d06'
>>>rgb_to_hex(99, 88, 77)
'#63584d'
>>>tup_rgb_to_hex((123, 45, 6))
'#7b2d06'
>>>tup_rgb_to_hex((99, 88, 77))
'#63584d'
>>>rgb_to_hex(*(123, 45, 6))
'#7b2d06'
>>>rgb_to_hex(*(99, 88, 77))
'#63584d'
The functions I've come up with are very simple, but intolerably slow. This code is a rare case where performance is a real concern. It will need to be called once per pixel, and my goal is to support the creation of images up to 50,000x30,000 pixels (1500000000 in total). 100 million executions take ~300 seconds:
>>>timeit.timeit(lambda: rgb_to_hex(255, 254, 253), number=int(1e8))
304.3993674000001
Which, unless my math is fubared, means this function alone will take 75 minutes in total for my extreme case.
I wrote two versions. The latter was to reduce redundancy (and since I'll be handling tuples anyways), but it was even slower, so I ended up just using unpacking on the first version:
# Takes a tuple instead
>>>timeit.timeit(lambda: tup_rgb_to_hex((255, 254, 253)), number=int(1e8))
342.8174099
# Unpacks arguments
>>>timeit.timeit(lambda: rgb_to_hex(*(255, 254, 253)), number=int(1e8))
308.64342439999973
The code:
from typing import Tuple
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def tup_rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
return "#" + "".join([_channel_to_hex(c) for c in rgb])
I'd prefer to be able to use the tup_
variant for cleanliness, but there may not be a good way to automate the iteration with acceptable amounts of overhead.
Any performance-related tips (or anything else if you see something) are welcome.
python performance python-3.x
$endgroup$
|
show 5 more comments
$begingroup$
I'm rewriting a full color Mandelbrot Set explorer in Python using tkinter. For it, I need to be able to convert a Tuple[int, int, int]
into a hex string in the form #123456
. Here are example uses of the two variants that I came up with:
>>>rgb_to_hex(123, 45, 6)
'#7b2d06'
>>>rgb_to_hex(99, 88, 77)
'#63584d'
>>>tup_rgb_to_hex((123, 45, 6))
'#7b2d06'
>>>tup_rgb_to_hex((99, 88, 77))
'#63584d'
>>>rgb_to_hex(*(123, 45, 6))
'#7b2d06'
>>>rgb_to_hex(*(99, 88, 77))
'#63584d'
The functions I've come up with are very simple, but intolerably slow. This code is a rare case where performance is a real concern. It will need to be called once per pixel, and my goal is to support the creation of images up to 50,000x30,000 pixels (1500000000 in total). 100 million executions take ~300 seconds:
>>>timeit.timeit(lambda: rgb_to_hex(255, 254, 253), number=int(1e8))
304.3993674000001
Which, unless my math is fubared, means this function alone will take 75 minutes in total for my extreme case.
I wrote two versions. The latter was to reduce redundancy (and since I'll be handling tuples anyways), but it was even slower, so I ended up just using unpacking on the first version:
# Takes a tuple instead
>>>timeit.timeit(lambda: tup_rgb_to_hex((255, 254, 253)), number=int(1e8))
342.8174099
# Unpacks arguments
>>>timeit.timeit(lambda: rgb_to_hex(*(255, 254, 253)), number=int(1e8))
308.64342439999973
The code:
from typing import Tuple
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def tup_rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
return "#" + "".join([_channel_to_hex(c) for c in rgb])
I'd prefer to be able to use the tup_
variant for cleanliness, but there may not be a good way to automate the iteration with acceptable amounts of overhead.
Any performance-related tips (or anything else if you see something) are welcome.
python performance python-3.x
$endgroup$
18
$begingroup$
It will need to be called once per pixel
- this is suspicious. What code is accepting a hex string? High-performance graphics code should be dealing in RGB byte triples packed into an int32.
$endgroup$
– Reinderien
Sep 19 at 1:32
1
$begingroup$
Put another way: I think you need to approach this from another angle. Make a framebuffer using a lower-level technology and pull that into tkinter wholesale.
$endgroup$
– Reinderien
Sep 19 at 1:38
3
$begingroup$
@Reinderien Canvas pixel coloring seems to be done exclusively using hex strings in tkinter unfortunately. Unless I can have it accept a buffer of existing data. I'll look into that tomorrow. Thanks.
$endgroup$
– Carcigenicate
Sep 19 at 2:00
3
$begingroup$
And I'm going to remove thenumber-guessing-game
tag. I'm not sure why that was added.
$endgroup$
– Carcigenicate
Sep 19 at 2:02
1
$begingroup$
Where are you getting those tuples? Perhaps there's an easy way to stay out of python for longer and therefore optimize better. Just calling any function 1.5e9 times is going to be slower than you want. For example, if you get them from a long continues array of some kind, numpy makes this trivial, especially since you can probably just shove numpy's own byte buffer into tkinter. I've had an issue like this myself once for displaying an image, and the solution was "let numpy handle it."
$endgroup$
– Gloweye
Sep 19 at 8:25
|
show 5 more comments
$begingroup$
I'm rewriting a full color Mandelbrot Set explorer in Python using tkinter. For it, I need to be able to convert a Tuple[int, int, int]
into a hex string in the form #123456
. Here are example uses of the two variants that I came up with:
>>>rgb_to_hex(123, 45, 6)
'#7b2d06'
>>>rgb_to_hex(99, 88, 77)
'#63584d'
>>>tup_rgb_to_hex((123, 45, 6))
'#7b2d06'
>>>tup_rgb_to_hex((99, 88, 77))
'#63584d'
>>>rgb_to_hex(*(123, 45, 6))
'#7b2d06'
>>>rgb_to_hex(*(99, 88, 77))
'#63584d'
The functions I've come up with are very simple, but intolerably slow. This code is a rare case where performance is a real concern. It will need to be called once per pixel, and my goal is to support the creation of images up to 50,000x30,000 pixels (1500000000 in total). 100 million executions take ~300 seconds:
>>>timeit.timeit(lambda: rgb_to_hex(255, 254, 253), number=int(1e8))
304.3993674000001
Which, unless my math is fubared, means this function alone will take 75 minutes in total for my extreme case.
I wrote two versions. The latter was to reduce redundancy (and since I'll be handling tuples anyways), but it was even slower, so I ended up just using unpacking on the first version:
# Takes a tuple instead
>>>timeit.timeit(lambda: tup_rgb_to_hex((255, 254, 253)), number=int(1e8))
342.8174099
# Unpacks arguments
>>>timeit.timeit(lambda: rgb_to_hex(*(255, 254, 253)), number=int(1e8))
308.64342439999973
The code:
from typing import Tuple
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def tup_rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
return "#" + "".join([_channel_to_hex(c) for c in rgb])
I'd prefer to be able to use the tup_
variant for cleanliness, but there may not be a good way to automate the iteration with acceptable amounts of overhead.
Any performance-related tips (or anything else if you see something) are welcome.
python performance python-3.x
$endgroup$
I'm rewriting a full color Mandelbrot Set explorer in Python using tkinter. For it, I need to be able to convert a Tuple[int, int, int]
into a hex string in the form #123456
. Here are example uses of the two variants that I came up with:
>>>rgb_to_hex(123, 45, 6)
'#7b2d06'
>>>rgb_to_hex(99, 88, 77)
'#63584d'
>>>tup_rgb_to_hex((123, 45, 6))
'#7b2d06'
>>>tup_rgb_to_hex((99, 88, 77))
'#63584d'
>>>rgb_to_hex(*(123, 45, 6))
'#7b2d06'
>>>rgb_to_hex(*(99, 88, 77))
'#63584d'
The functions I've come up with are very simple, but intolerably slow. This code is a rare case where performance is a real concern. It will need to be called once per pixel, and my goal is to support the creation of images up to 50,000x30,000 pixels (1500000000 in total). 100 million executions take ~300 seconds:
>>>timeit.timeit(lambda: rgb_to_hex(255, 254, 253), number=int(1e8))
304.3993674000001
Which, unless my math is fubared, means this function alone will take 75 minutes in total for my extreme case.
I wrote two versions. The latter was to reduce redundancy (and since I'll be handling tuples anyways), but it was even slower, so I ended up just using unpacking on the first version:
# Takes a tuple instead
>>>timeit.timeit(lambda: tup_rgb_to_hex((255, 254, 253)), number=int(1e8))
342.8174099
# Unpacks arguments
>>>timeit.timeit(lambda: rgb_to_hex(*(255, 254, 253)), number=int(1e8))
308.64342439999973
The code:
from typing import Tuple
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def tup_rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
return "#" + "".join([_channel_to_hex(c) for c in rgb])
I'd prefer to be able to use the tup_
variant for cleanliness, but there may not be a good way to automate the iteration with acceptable amounts of overhead.
Any performance-related tips (or anything else if you see something) are welcome.
python performance python-3.x
python performance python-3.x
edited Sep 19 at 2:01
Carcigenicate
asked Sep 19 at 0:16
CarcigenicateCarcigenicate
10.6k1 gold badge20 silver badges50 bronze badges
10.6k1 gold badge20 silver badges50 bronze badges
18
$begingroup$
It will need to be called once per pixel
- this is suspicious. What code is accepting a hex string? High-performance graphics code should be dealing in RGB byte triples packed into an int32.
$endgroup$
– Reinderien
Sep 19 at 1:32
1
$begingroup$
Put another way: I think you need to approach this from another angle. Make a framebuffer using a lower-level technology and pull that into tkinter wholesale.
$endgroup$
– Reinderien
Sep 19 at 1:38
3
$begingroup$
@Reinderien Canvas pixel coloring seems to be done exclusively using hex strings in tkinter unfortunately. Unless I can have it accept a buffer of existing data. I'll look into that tomorrow. Thanks.
$endgroup$
– Carcigenicate
Sep 19 at 2:00
3
$begingroup$
And I'm going to remove thenumber-guessing-game
tag. I'm not sure why that was added.
$endgroup$
– Carcigenicate
Sep 19 at 2:02
1
$begingroup$
Where are you getting those tuples? Perhaps there's an easy way to stay out of python for longer and therefore optimize better. Just calling any function 1.5e9 times is going to be slower than you want. For example, if you get them from a long continues array of some kind, numpy makes this trivial, especially since you can probably just shove numpy's own byte buffer into tkinter. I've had an issue like this myself once for displaying an image, and the solution was "let numpy handle it."
$endgroup$
– Gloweye
Sep 19 at 8:25
|
show 5 more comments
18
$begingroup$
It will need to be called once per pixel
- this is suspicious. What code is accepting a hex string? High-performance graphics code should be dealing in RGB byte triples packed into an int32.
$endgroup$
– Reinderien
Sep 19 at 1:32
1
$begingroup$
Put another way: I think you need to approach this from another angle. Make a framebuffer using a lower-level technology and pull that into tkinter wholesale.
$endgroup$
– Reinderien
Sep 19 at 1:38
3
$begingroup$
@Reinderien Canvas pixel coloring seems to be done exclusively using hex strings in tkinter unfortunately. Unless I can have it accept a buffer of existing data. I'll look into that tomorrow. Thanks.
$endgroup$
– Carcigenicate
Sep 19 at 2:00
3
$begingroup$
And I'm going to remove thenumber-guessing-game
tag. I'm not sure why that was added.
$endgroup$
– Carcigenicate
Sep 19 at 2:02
1
$begingroup$
Where are you getting those tuples? Perhaps there's an easy way to stay out of python for longer and therefore optimize better. Just calling any function 1.5e9 times is going to be slower than you want. For example, if you get them from a long continues array of some kind, numpy makes this trivial, especially since you can probably just shove numpy's own byte buffer into tkinter. I've had an issue like this myself once for displaying an image, and the solution was "let numpy handle it."
$endgroup$
– Gloweye
Sep 19 at 8:25
18
18
$begingroup$
It will need to be called once per pixel
- this is suspicious. What code is accepting a hex string? High-performance graphics code should be dealing in RGB byte triples packed into an int32.$endgroup$
– Reinderien
Sep 19 at 1:32
$begingroup$
It will need to be called once per pixel
- this is suspicious. What code is accepting a hex string? High-performance graphics code should be dealing in RGB byte triples packed into an int32.$endgroup$
– Reinderien
Sep 19 at 1:32
1
1
$begingroup$
Put another way: I think you need to approach this from another angle. Make a framebuffer using a lower-level technology and pull that into tkinter wholesale.
$endgroup$
– Reinderien
Sep 19 at 1:38
$begingroup$
Put another way: I think you need to approach this from another angle. Make a framebuffer using a lower-level technology and pull that into tkinter wholesale.
$endgroup$
– Reinderien
Sep 19 at 1:38
3
3
$begingroup$
@Reinderien Canvas pixel coloring seems to be done exclusively using hex strings in tkinter unfortunately. Unless I can have it accept a buffer of existing data. I'll look into that tomorrow. Thanks.
$endgroup$
– Carcigenicate
Sep 19 at 2:00
$begingroup$
@Reinderien Canvas pixel coloring seems to be done exclusively using hex strings in tkinter unfortunately. Unless I can have it accept a buffer of existing data. I'll look into that tomorrow. Thanks.
$endgroup$
– Carcigenicate
Sep 19 at 2:00
3
3
$begingroup$
And I'm going to remove the
number-guessing-game
tag. I'm not sure why that was added.$endgroup$
– Carcigenicate
Sep 19 at 2:02
$begingroup$
And I'm going to remove the
number-guessing-game
tag. I'm not sure why that was added.$endgroup$
– Carcigenicate
Sep 19 at 2:02
1
1
$begingroup$
Where are you getting those tuples? Perhaps there's an easy way to stay out of python for longer and therefore optimize better. Just calling any function 1.5e9 times is going to be slower than you want. For example, if you get them from a long continues array of some kind, numpy makes this trivial, especially since you can probably just shove numpy's own byte buffer into tkinter. I've had an issue like this myself once for displaying an image, and the solution was "let numpy handle it."
$endgroup$
– Gloweye
Sep 19 at 8:25
$begingroup$
Where are you getting those tuples? Perhaps there's an easy way to stay out of python for longer and therefore optimize better. Just calling any function 1.5e9 times is going to be slower than you want. For example, if you get them from a long continues array of some kind, numpy makes this trivial, especially since you can probably just shove numpy's own byte buffer into tkinter. I've had an issue like this myself once for displaying an image, and the solution was "let numpy handle it."
$endgroup$
– Gloweye
Sep 19 at 8:25
|
show 5 more comments
5 Answers
5
active
oldest
votes
$begingroup$
You seem to be jumping through some unnecessary hoops. Just format a string directly:
from timeit import timeit
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def direct_format(r, g, b):
return f'#r:02xg:02xb:02x'
def one_word(r, g, b):
rgb = r<<16 | g<<8 | b
return f'#rgb:06x'
def main():
N = 100000
methods = (
rgb_to_hex,
direct_format,
one_word,
)
for method in methods:
hex = method(1, 2, 255)
assert '#0102ff' == hex
def run():
return method(1, 2, 255)
dur = timeit(run, number=N)
print(f'method.__name__:15 1e6*dur/N:.2f us')
main()
produces:
rgb_to_hex 6.75 us
direct_format 3.14 us
one_word 2.74 us
That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.
$endgroup$
1
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
add a comment
|
$begingroup$
Another approach to generate the hex string is to directly reuse methods of format strings rather than writing your own function.
rgb_to_hex = "#:02x:02x:02x".format # rgb_to_hex(r, g, b) expands to "...".format(r, g, b)
rgb_tup_to_hex = "#%02x%02x%02x".__mod__ # rgb_tup_to_hex((r, g, b)) expands to "..." % (r, g, b)
These are faster (rgb_to_hex_orig
is renamed from the rgb_to_hex
function in the question):
rgb_tup = (0x20, 0xFB, 0xC2)
%timeit rgb_to_hex_orig(*rgb_tup)
%timeit direct_format(*rgb_tup)
%timeit one_word(*rgb_tup)
%timeit rgb_to_hex(*rgb_tup)
%timeit rgb_tup_to_hex(rgb_tup)
Results:
1.57 µs ± 5.14 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.18 µs ± 5.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
704 ns ± 3.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
672 ns ± 4.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
502 ns ± 7.23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
rgb_tup_to_hex
is the fastest partially due to it takes a tuple directly as its argument and avoids the small overhead of unpacking arguments.
However, I doubt these improvements would help solve your problem given its magnitude.
Using the Pillow / PIL library, pixel values can be directly set based on indices using tuples. Therefore converting tuples to strings are not really necessary. Here are examples showing basics of displaying PIL
images in tkinter
. This is likely still slow if the changes are done pixel by pixel. For extensive changes, the ImageDraw module or Image.putdata could be used.
$endgroup$
add a comment
|
$begingroup$
memoization
The _channel_to_hex
is called 3 times per pixel. It only takes 256 different inputs, so a logical first step would be to memoize the results. This can be done with either functools.lru_cache
from functools import lru_cache
@lru_cache(None)
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
This already reduces the time needed with about a 3rd
An alternative is using a dict:
color_hexes =
color_val: hex(color_val)[2:].zfill(2)
for color_val in range(256)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
If the color-tuples are also limited (256**3 in worst case), so these can also be memoized
color_tuple_hexes =
rgb_to_hex_dict(*color_tuple)
for color_tuple in itertools.product(range(256), repeat=3)
This takes about 15 seconds on my machine, but only needs to be done once.
If only a limited set of tuples is used, you can also use lru_cache
@lru_cache(None)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
numpy
if you have your data in a 3-dimensional numpy array, for example:
color_data = np.random.randint(256, size=(10,10,3))
You could do something like this:
coeffs = np.array([256**i for i in range(3)])
np_hex = (color_data * coeffs[np.newaxis, np.newaxis, :]).sum(axis=2)
np.vectorize(lambda x: "#" + hex(x)[2:].zfill(6))(np_hex)
$endgroup$
add a comment
|
$begingroup$
Numpy is your best friend.
Given your comment:
The tuples are produced by "color scheme" functions. The functions take the (real, imaginary) coordinates of the pixel and how many iterations it took that pixel to fail, and return a three-tuple. They could return anything to indicate the color (that code is completely in my control), I just thought a three-tuple would by simplest. In theory, I could expect the functions to directly return a hex string, but that's just kicking the can down the road a bit since they need to be able to generate the string somehow.
Create a numpy array for the image you're going to create, then just assign your values into the array directly. Something like this:
import numpy as np
image = np.empty(shape=(final_image.ysize, final_image.xsize, 3), dtype=np.uint8)
# And instead of calling a function, assign to the array simply like:
image[x_coor, y_coor] = color_tuple
# Or if you really need a function:
image.__setitem__((x_coor, y_coor), color_tuple) # I haven't tested this with numpy, but something like it should work.
You do need to make sure that your arrays are in the same shape as tkinter expects it's images, though. And if you can make another shortcut to put the data into the array sooner, take it.
If you're doing an action this often, then you need to cut out function calls and such as often as you can. If possible, make the slice assignments bigger to set area's at the same time.
$endgroup$
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed usingarray
,zeros
orempty
.
$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.
$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid ofnp.ndarray(...)
.
$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
I think you need parentheses aroundx_coor, y_coor
to make it a tuple.
$endgroup$
– Solomon Ucko
Sep 20 at 1:32
|
show 1 more comment
$begingroup$
Python compilers for performance
Nuitka
Nuitka compiles any and all Python code into faster architecture-specific C++ code. Nuitka's generated code is faster.
Cython
Cython can compiles any and all Python code into platform-indepndent C code. However, where it really shines is because you can annotate your Cython functions with C types and get a performance boost out of that.
PyPy
PyPy is a JIT that compiles pure Python code. Sometimes it can produce good code, but it has a slow startup time. Although PyPy probably won't give you C-like or FORTRAN-like speeds, it can sometimes double or triple the execution speed of performance-critical sections.
However, PyPy is low on developers, and as such, it does not yet support Python versions 3.7 or 3.8. Most libraries are still written with 3.6 compatibility.
Numba
Numba compiles a small subset of Python. It can achieve C-like or FORTRAN-like speeds with this subset-- when tuned properly, it can automatically parallelize and automatically use the GPU. However, you won't really be writing your code in Python, but in Numba.
Alternatives
You can write performance-critical code in another programming language.
One to consider would be D, a modern programming language with excellent C compatibility.
Python integrates easily with languages C. In fact, you can load dynamic libraries written in C* into Python with no glue code.
*D should be able to do this with extern(C):
and -betterC
.
$endgroup$
add a comment
|
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/4.0/"u003ecc by-sa 4.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f229282%2fperformance-for-simple-code-that-converts-a-rgb-tuple-to-hex-string%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
You seem to be jumping through some unnecessary hoops. Just format a string directly:
from timeit import timeit
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def direct_format(r, g, b):
return f'#r:02xg:02xb:02x'
def one_word(r, g, b):
rgb = r<<16 | g<<8 | b
return f'#rgb:06x'
def main():
N = 100000
methods = (
rgb_to_hex,
direct_format,
one_word,
)
for method in methods:
hex = method(1, 2, 255)
assert '#0102ff' == hex
def run():
return method(1, 2, 255)
dur = timeit(run, number=N)
print(f'method.__name__:15 1e6*dur/N:.2f us')
main()
produces:
rgb_to_hex 6.75 us
direct_format 3.14 us
one_word 2.74 us
That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.
$endgroup$
1
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
add a comment
|
$begingroup$
You seem to be jumping through some unnecessary hoops. Just format a string directly:
from timeit import timeit
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def direct_format(r, g, b):
return f'#r:02xg:02xb:02x'
def one_word(r, g, b):
rgb = r<<16 | g<<8 | b
return f'#rgb:06x'
def main():
N = 100000
methods = (
rgb_to_hex,
direct_format,
one_word,
)
for method in methods:
hex = method(1, 2, 255)
assert '#0102ff' == hex
def run():
return method(1, 2, 255)
dur = timeit(run, number=N)
print(f'method.__name__:15 1e6*dur/N:.2f us')
main()
produces:
rgb_to_hex 6.75 us
direct_format 3.14 us
one_word 2.74 us
That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.
$endgroup$
1
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
add a comment
|
$begingroup$
You seem to be jumping through some unnecessary hoops. Just format a string directly:
from timeit import timeit
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def direct_format(r, g, b):
return f'#r:02xg:02xb:02x'
def one_word(r, g, b):
rgb = r<<16 | g<<8 | b
return f'#rgb:06x'
def main():
N = 100000
methods = (
rgb_to_hex,
direct_format,
one_word,
)
for method in methods:
hex = method(1, 2, 255)
assert '#0102ff' == hex
def run():
return method(1, 2, 255)
dur = timeit(run, number=N)
print(f'method.__name__:15 1e6*dur/N:.2f us')
main()
produces:
rgb_to_hex 6.75 us
direct_format 3.14 us
one_word 2.74 us
That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.
$endgroup$
You seem to be jumping through some unnecessary hoops. Just format a string directly:
from timeit import timeit
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
def rgb_to_hex(red: int, green: int, blue: int) -> str:
return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)
def direct_format(r, g, b):
return f'#r:02xg:02xb:02x'
def one_word(r, g, b):
rgb = r<<16 | g<<8 | b
return f'#rgb:06x'
def main():
N = 100000
methods = (
rgb_to_hex,
direct_format,
one_word,
)
for method in methods:
hex = method(1, 2, 255)
assert '#0102ff' == hex
def run():
return method(1, 2, 255)
dur = timeit(run, number=N)
print(f'method.__name__:15 1e6*dur/N:.2f us')
main()
produces:
rgb_to_hex 6.75 us
direct_format 3.14 us
one_word 2.74 us
That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.
edited Sep 19 at 2:47
answered Sep 19 at 2:14
ReinderienReinderien
12.8k22 silver badges50 bronze badges
12.8k22 silver badges50 bronze badges
1
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
add a comment
|
1
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
1
1
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
$begingroup$
Good suggestions, but I ended up just learning how to use Pillow (basically your suggestion at the bottom). You can import Pillow image objects directly into tkinter. It ended up being much cleaner. Thank you.
$endgroup$
– Carcigenicate
Sep 24 at 15:38
add a comment
|
$begingroup$
Another approach to generate the hex string is to directly reuse methods of format strings rather than writing your own function.
rgb_to_hex = "#:02x:02x:02x".format # rgb_to_hex(r, g, b) expands to "...".format(r, g, b)
rgb_tup_to_hex = "#%02x%02x%02x".__mod__ # rgb_tup_to_hex((r, g, b)) expands to "..." % (r, g, b)
These are faster (rgb_to_hex_orig
is renamed from the rgb_to_hex
function in the question):
rgb_tup = (0x20, 0xFB, 0xC2)
%timeit rgb_to_hex_orig(*rgb_tup)
%timeit direct_format(*rgb_tup)
%timeit one_word(*rgb_tup)
%timeit rgb_to_hex(*rgb_tup)
%timeit rgb_tup_to_hex(rgb_tup)
Results:
1.57 µs ± 5.14 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.18 µs ± 5.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
704 ns ± 3.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
672 ns ± 4.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
502 ns ± 7.23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
rgb_tup_to_hex
is the fastest partially due to it takes a tuple directly as its argument and avoids the small overhead of unpacking arguments.
However, I doubt these improvements would help solve your problem given its magnitude.
Using the Pillow / PIL library, pixel values can be directly set based on indices using tuples. Therefore converting tuples to strings are not really necessary. Here are examples showing basics of displaying PIL
images in tkinter
. This is likely still slow if the changes are done pixel by pixel. For extensive changes, the ImageDraw module or Image.putdata could be used.
$endgroup$
add a comment
|
$begingroup$
Another approach to generate the hex string is to directly reuse methods of format strings rather than writing your own function.
rgb_to_hex = "#:02x:02x:02x".format # rgb_to_hex(r, g, b) expands to "...".format(r, g, b)
rgb_tup_to_hex = "#%02x%02x%02x".__mod__ # rgb_tup_to_hex((r, g, b)) expands to "..." % (r, g, b)
These are faster (rgb_to_hex_orig
is renamed from the rgb_to_hex
function in the question):
rgb_tup = (0x20, 0xFB, 0xC2)
%timeit rgb_to_hex_orig(*rgb_tup)
%timeit direct_format(*rgb_tup)
%timeit one_word(*rgb_tup)
%timeit rgb_to_hex(*rgb_tup)
%timeit rgb_tup_to_hex(rgb_tup)
Results:
1.57 µs ± 5.14 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.18 µs ± 5.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
704 ns ± 3.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
672 ns ± 4.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
502 ns ± 7.23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
rgb_tup_to_hex
is the fastest partially due to it takes a tuple directly as its argument and avoids the small overhead of unpacking arguments.
However, I doubt these improvements would help solve your problem given its magnitude.
Using the Pillow / PIL library, pixel values can be directly set based on indices using tuples. Therefore converting tuples to strings are not really necessary. Here are examples showing basics of displaying PIL
images in tkinter
. This is likely still slow if the changes are done pixel by pixel. For extensive changes, the ImageDraw module or Image.putdata could be used.
$endgroup$
add a comment
|
$begingroup$
Another approach to generate the hex string is to directly reuse methods of format strings rather than writing your own function.
rgb_to_hex = "#:02x:02x:02x".format # rgb_to_hex(r, g, b) expands to "...".format(r, g, b)
rgb_tup_to_hex = "#%02x%02x%02x".__mod__ # rgb_tup_to_hex((r, g, b)) expands to "..." % (r, g, b)
These are faster (rgb_to_hex_orig
is renamed from the rgb_to_hex
function in the question):
rgb_tup = (0x20, 0xFB, 0xC2)
%timeit rgb_to_hex_orig(*rgb_tup)
%timeit direct_format(*rgb_tup)
%timeit one_word(*rgb_tup)
%timeit rgb_to_hex(*rgb_tup)
%timeit rgb_tup_to_hex(rgb_tup)
Results:
1.57 µs ± 5.14 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.18 µs ± 5.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
704 ns ± 3.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
672 ns ± 4.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
502 ns ± 7.23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
rgb_tup_to_hex
is the fastest partially due to it takes a tuple directly as its argument and avoids the small overhead of unpacking arguments.
However, I doubt these improvements would help solve your problem given its magnitude.
Using the Pillow / PIL library, pixel values can be directly set based on indices using tuples. Therefore converting tuples to strings are not really necessary. Here are examples showing basics of displaying PIL
images in tkinter
. This is likely still slow if the changes are done pixel by pixel. For extensive changes, the ImageDraw module or Image.putdata could be used.
$endgroup$
Another approach to generate the hex string is to directly reuse methods of format strings rather than writing your own function.
rgb_to_hex = "#:02x:02x:02x".format # rgb_to_hex(r, g, b) expands to "...".format(r, g, b)
rgb_tup_to_hex = "#%02x%02x%02x".__mod__ # rgb_tup_to_hex((r, g, b)) expands to "..." % (r, g, b)
These are faster (rgb_to_hex_orig
is renamed from the rgb_to_hex
function in the question):
rgb_tup = (0x20, 0xFB, 0xC2)
%timeit rgb_to_hex_orig(*rgb_tup)
%timeit direct_format(*rgb_tup)
%timeit one_word(*rgb_tup)
%timeit rgb_to_hex(*rgb_tup)
%timeit rgb_tup_to_hex(rgb_tup)
Results:
1.57 µs ± 5.14 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.18 µs ± 5.34 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
704 ns ± 3.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
672 ns ± 4.54 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
502 ns ± 7.23 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
rgb_tup_to_hex
is the fastest partially due to it takes a tuple directly as its argument and avoids the small overhead of unpacking arguments.
However, I doubt these improvements would help solve your problem given its magnitude.
Using the Pillow / PIL library, pixel values can be directly set based on indices using tuples. Therefore converting tuples to strings are not really necessary. Here are examples showing basics of displaying PIL
images in tkinter
. This is likely still slow if the changes are done pixel by pixel. For extensive changes, the ImageDraw module or Image.putdata could be used.
edited Sep 19 at 12:33
answered Sep 19 at 12:11
GZ0GZ0
1,2491 silver badge10 bronze badges
1,2491 silver badge10 bronze badges
add a comment
|
add a comment
|
$begingroup$
memoization
The _channel_to_hex
is called 3 times per pixel. It only takes 256 different inputs, so a logical first step would be to memoize the results. This can be done with either functools.lru_cache
from functools import lru_cache
@lru_cache(None)
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
This already reduces the time needed with about a 3rd
An alternative is using a dict:
color_hexes =
color_val: hex(color_val)[2:].zfill(2)
for color_val in range(256)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
If the color-tuples are also limited (256**3 in worst case), so these can also be memoized
color_tuple_hexes =
rgb_to_hex_dict(*color_tuple)
for color_tuple in itertools.product(range(256), repeat=3)
This takes about 15 seconds on my machine, but only needs to be done once.
If only a limited set of tuples is used, you can also use lru_cache
@lru_cache(None)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
numpy
if you have your data in a 3-dimensional numpy array, for example:
color_data = np.random.randint(256, size=(10,10,3))
You could do something like this:
coeffs = np.array([256**i for i in range(3)])
np_hex = (color_data * coeffs[np.newaxis, np.newaxis, :]).sum(axis=2)
np.vectorize(lambda x: "#" + hex(x)[2:].zfill(6))(np_hex)
$endgroup$
add a comment
|
$begingroup$
memoization
The _channel_to_hex
is called 3 times per pixel. It only takes 256 different inputs, so a logical first step would be to memoize the results. This can be done with either functools.lru_cache
from functools import lru_cache
@lru_cache(None)
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
This already reduces the time needed with about a 3rd
An alternative is using a dict:
color_hexes =
color_val: hex(color_val)[2:].zfill(2)
for color_val in range(256)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
If the color-tuples are also limited (256**3 in worst case), so these can also be memoized
color_tuple_hexes =
rgb_to_hex_dict(*color_tuple)
for color_tuple in itertools.product(range(256), repeat=3)
This takes about 15 seconds on my machine, but only needs to be done once.
If only a limited set of tuples is used, you can also use lru_cache
@lru_cache(None)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
numpy
if you have your data in a 3-dimensional numpy array, for example:
color_data = np.random.randint(256, size=(10,10,3))
You could do something like this:
coeffs = np.array([256**i for i in range(3)])
np_hex = (color_data * coeffs[np.newaxis, np.newaxis, :]).sum(axis=2)
np.vectorize(lambda x: "#" + hex(x)[2:].zfill(6))(np_hex)
$endgroup$
add a comment
|
$begingroup$
memoization
The _channel_to_hex
is called 3 times per pixel. It only takes 256 different inputs, so a logical first step would be to memoize the results. This can be done with either functools.lru_cache
from functools import lru_cache
@lru_cache(None)
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
This already reduces the time needed with about a 3rd
An alternative is using a dict:
color_hexes =
color_val: hex(color_val)[2:].zfill(2)
for color_val in range(256)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
If the color-tuples are also limited (256**3 in worst case), so these can also be memoized
color_tuple_hexes =
rgb_to_hex_dict(*color_tuple)
for color_tuple in itertools.product(range(256), repeat=3)
This takes about 15 seconds on my machine, but only needs to be done once.
If only a limited set of tuples is used, you can also use lru_cache
@lru_cache(None)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
numpy
if you have your data in a 3-dimensional numpy array, for example:
color_data = np.random.randint(256, size=(10,10,3))
You could do something like this:
coeffs = np.array([256**i for i in range(3)])
np_hex = (color_data * coeffs[np.newaxis, np.newaxis, :]).sum(axis=2)
np.vectorize(lambda x: "#" + hex(x)[2:].zfill(6))(np_hex)
$endgroup$
memoization
The _channel_to_hex
is called 3 times per pixel. It only takes 256 different inputs, so a logical first step would be to memoize the results. This can be done with either functools.lru_cache
from functools import lru_cache
@lru_cache(None)
def _channel_to_hex(color_val: int) -> str:
raw: str = hex(color_val)[2:]
return raw.zfill(2)
This already reduces the time needed with about a 3rd
An alternative is using a dict:
color_hexes =
color_val: hex(color_val)[2:].zfill(2)
for color_val in range(256)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
If the color-tuples are also limited (256**3 in worst case), so these can also be memoized
color_tuple_hexes =
rgb_to_hex_dict(*color_tuple)
for color_tuple in itertools.product(range(256), repeat=3)
This takes about 15 seconds on my machine, but only needs to be done once.
If only a limited set of tuples is used, you can also use lru_cache
@lru_cache(None)
def rgb_to_hex_dict(red: int, green: int, blue: int) -> str:
return "#" + color_hexes[red] + color_hexes[green] + color_hexes[blue]
numpy
if you have your data in a 3-dimensional numpy array, for example:
color_data = np.random.randint(256, size=(10,10,3))
You could do something like this:
coeffs = np.array([256**i for i in range(3)])
np_hex = (color_data * coeffs[np.newaxis, np.newaxis, :]).sum(axis=2)
np.vectorize(lambda x: "#" + hex(x)[2:].zfill(6))(np_hex)
edited Sep 19 at 13:19
answered Sep 19 at 12:57
Maarten FabréMaarten Fabré
7,0321 gold badge9 silver badges24 bronze badges
7,0321 gold badge9 silver badges24 bronze badges
add a comment
|
add a comment
|
$begingroup$
Numpy is your best friend.
Given your comment:
The tuples are produced by "color scheme" functions. The functions take the (real, imaginary) coordinates of the pixel and how many iterations it took that pixel to fail, and return a three-tuple. They could return anything to indicate the color (that code is completely in my control), I just thought a three-tuple would by simplest. In theory, I could expect the functions to directly return a hex string, but that's just kicking the can down the road a bit since they need to be able to generate the string somehow.
Create a numpy array for the image you're going to create, then just assign your values into the array directly. Something like this:
import numpy as np
image = np.empty(shape=(final_image.ysize, final_image.xsize, 3), dtype=np.uint8)
# And instead of calling a function, assign to the array simply like:
image[x_coor, y_coor] = color_tuple
# Or if you really need a function:
image.__setitem__((x_coor, y_coor), color_tuple) # I haven't tested this with numpy, but something like it should work.
You do need to make sure that your arrays are in the same shape as tkinter expects it's images, though. And if you can make another shortcut to put the data into the array sooner, take it.
If you're doing an action this often, then you need to cut out function calls and such as often as you can. If possible, make the slice assignments bigger to set area's at the same time.
$endgroup$
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed usingarray
,zeros
orempty
.
$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.
$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid ofnp.ndarray(...)
.
$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
I think you need parentheses aroundx_coor, y_coor
to make it a tuple.
$endgroup$
– Solomon Ucko
Sep 20 at 1:32
|
show 1 more comment
$begingroup$
Numpy is your best friend.
Given your comment:
The tuples are produced by "color scheme" functions. The functions take the (real, imaginary) coordinates of the pixel and how many iterations it took that pixel to fail, and return a three-tuple. They could return anything to indicate the color (that code is completely in my control), I just thought a three-tuple would by simplest. In theory, I could expect the functions to directly return a hex string, but that's just kicking the can down the road a bit since they need to be able to generate the string somehow.
Create a numpy array for the image you're going to create, then just assign your values into the array directly. Something like this:
import numpy as np
image = np.empty(shape=(final_image.ysize, final_image.xsize, 3), dtype=np.uint8)
# And instead of calling a function, assign to the array simply like:
image[x_coor, y_coor] = color_tuple
# Or if you really need a function:
image.__setitem__((x_coor, y_coor), color_tuple) # I haven't tested this with numpy, but something like it should work.
You do need to make sure that your arrays are in the same shape as tkinter expects it's images, though. And if you can make another shortcut to put the data into the array sooner, take it.
If you're doing an action this often, then you need to cut out function calls and such as often as you can. If possible, make the slice assignments bigger to set area's at the same time.
$endgroup$
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed usingarray
,zeros
orempty
.
$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.
$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid ofnp.ndarray(...)
.
$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
I think you need parentheses aroundx_coor, y_coor
to make it a tuple.
$endgroup$
– Solomon Ucko
Sep 20 at 1:32
|
show 1 more comment
$begingroup$
Numpy is your best friend.
Given your comment:
The tuples are produced by "color scheme" functions. The functions take the (real, imaginary) coordinates of the pixel and how many iterations it took that pixel to fail, and return a three-tuple. They could return anything to indicate the color (that code is completely in my control), I just thought a three-tuple would by simplest. In theory, I could expect the functions to directly return a hex string, but that's just kicking the can down the road a bit since they need to be able to generate the string somehow.
Create a numpy array for the image you're going to create, then just assign your values into the array directly. Something like this:
import numpy as np
image = np.empty(shape=(final_image.ysize, final_image.xsize, 3), dtype=np.uint8)
# And instead of calling a function, assign to the array simply like:
image[x_coor, y_coor] = color_tuple
# Or if you really need a function:
image.__setitem__((x_coor, y_coor), color_tuple) # I haven't tested this with numpy, but something like it should work.
You do need to make sure that your arrays are in the same shape as tkinter expects it's images, though. And if you can make another shortcut to put the data into the array sooner, take it.
If you're doing an action this often, then you need to cut out function calls and such as often as you can. If possible, make the slice assignments bigger to set area's at the same time.
$endgroup$
Numpy is your best friend.
Given your comment:
The tuples are produced by "color scheme" functions. The functions take the (real, imaginary) coordinates of the pixel and how many iterations it took that pixel to fail, and return a three-tuple. They could return anything to indicate the color (that code is completely in my control), I just thought a three-tuple would by simplest. In theory, I could expect the functions to directly return a hex string, but that's just kicking the can down the road a bit since they need to be able to generate the string somehow.
Create a numpy array for the image you're going to create, then just assign your values into the array directly. Something like this:
import numpy as np
image = np.empty(shape=(final_image.ysize, final_image.xsize, 3), dtype=np.uint8)
# And instead of calling a function, assign to the array simply like:
image[x_coor, y_coor] = color_tuple
# Or if you really need a function:
image.__setitem__((x_coor, y_coor), color_tuple) # I haven't tested this with numpy, but something like it should work.
You do need to make sure that your arrays are in the same shape as tkinter expects it's images, though. And if you can make another shortcut to put the data into the array sooner, take it.
If you're doing an action this often, then you need to cut out function calls and such as often as you can. If possible, make the slice assignments bigger to set area's at the same time.
edited Sep 20 at 5:05
answered Sep 19 at 14:15
GloweyeGloweye
1,7055 silver badges19 bronze badges
1,7055 silver badges19 bronze badges
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed usingarray
,zeros
orempty
.
$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.
$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid ofnp.ndarray(...)
.
$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
I think you need parentheses aroundx_coor, y_coor
to make it a tuple.
$endgroup$
– Solomon Ucko
Sep 20 at 1:32
|
show 1 more comment
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed usingarray
,zeros
orempty
.
$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.
$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid ofnp.ndarray(...)
.
$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
I think you need parentheses aroundx_coor, y_coor
to make it a tuple.
$endgroup$
– Solomon Ucko
Sep 20 at 1:32
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed using array
, zeros
or empty
.$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
np.ndarray(...)
is rarely used directly. From doc: Arrays should be constructed using array
, zeros
or empty
.$endgroup$
– GZ0
Sep 19 at 14:31
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
Yes, you'll get a buffer filled with bogus data. But if you fill it yourself anyway, then it doesn't really matter what you use. I just grabbed the first thing that came to mind. Do you think it's bad practice if you fill your array entirely anyway ?
$endgroup$
– Gloweye
Sep 19 at 14:32
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,
np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
In this case I do not think there is much difference in terms of the functionality or performance. As the doc suggests,
np.ndarray
is a low-level method and it is recommended to use those high-level APIs instead.$endgroup$
– GZ0
Sep 19 at 14:41
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid of np.ndarray(...)
.$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
np.empty(...)
is good candidate to express what you are doing in case you want to follow @Gloweye's comment and get rid of np.ndarray(...)
.$endgroup$
– AlexV
Sep 19 at 15:04
$begingroup$
I think you need parentheses around
x_coor, y_coor
to make it a tuple.$endgroup$
– Solomon Ucko
Sep 20 at 1:32
$begingroup$
I think you need parentheses around
x_coor, y_coor
to make it a tuple.$endgroup$
– Solomon Ucko
Sep 20 at 1:32
|
show 1 more comment
$begingroup$
Python compilers for performance
Nuitka
Nuitka compiles any and all Python code into faster architecture-specific C++ code. Nuitka's generated code is faster.
Cython
Cython can compiles any and all Python code into platform-indepndent C code. However, where it really shines is because you can annotate your Cython functions with C types and get a performance boost out of that.
PyPy
PyPy is a JIT that compiles pure Python code. Sometimes it can produce good code, but it has a slow startup time. Although PyPy probably won't give you C-like or FORTRAN-like speeds, it can sometimes double or triple the execution speed of performance-critical sections.
However, PyPy is low on developers, and as such, it does not yet support Python versions 3.7 or 3.8. Most libraries are still written with 3.6 compatibility.
Numba
Numba compiles a small subset of Python. It can achieve C-like or FORTRAN-like speeds with this subset-- when tuned properly, it can automatically parallelize and automatically use the GPU. However, you won't really be writing your code in Python, but in Numba.
Alternatives
You can write performance-critical code in another programming language.
One to consider would be D, a modern programming language with excellent C compatibility.
Python integrates easily with languages C. In fact, you can load dynamic libraries written in C* into Python with no glue code.
*D should be able to do this with extern(C):
and -betterC
.
$endgroup$
add a comment
|
$begingroup$
Python compilers for performance
Nuitka
Nuitka compiles any and all Python code into faster architecture-specific C++ code. Nuitka's generated code is faster.
Cython
Cython can compiles any and all Python code into platform-indepndent C code. However, where it really shines is because you can annotate your Cython functions with C types and get a performance boost out of that.
PyPy
PyPy is a JIT that compiles pure Python code. Sometimes it can produce good code, but it has a slow startup time. Although PyPy probably won't give you C-like or FORTRAN-like speeds, it can sometimes double or triple the execution speed of performance-critical sections.
However, PyPy is low on developers, and as such, it does not yet support Python versions 3.7 or 3.8. Most libraries are still written with 3.6 compatibility.
Numba
Numba compiles a small subset of Python. It can achieve C-like or FORTRAN-like speeds with this subset-- when tuned properly, it can automatically parallelize and automatically use the GPU. However, you won't really be writing your code in Python, but in Numba.
Alternatives
You can write performance-critical code in another programming language.
One to consider would be D, a modern programming language with excellent C compatibility.
Python integrates easily with languages C. In fact, you can load dynamic libraries written in C* into Python with no glue code.
*D should be able to do this with extern(C):
and -betterC
.
$endgroup$
add a comment
|
$begingroup$
Python compilers for performance
Nuitka
Nuitka compiles any and all Python code into faster architecture-specific C++ code. Nuitka's generated code is faster.
Cython
Cython can compiles any and all Python code into platform-indepndent C code. However, where it really shines is because you can annotate your Cython functions with C types and get a performance boost out of that.
PyPy
PyPy is a JIT that compiles pure Python code. Sometimes it can produce good code, but it has a slow startup time. Although PyPy probably won't give you C-like or FORTRAN-like speeds, it can sometimes double or triple the execution speed of performance-critical sections.
However, PyPy is low on developers, and as such, it does not yet support Python versions 3.7 or 3.8. Most libraries are still written with 3.6 compatibility.
Numba
Numba compiles a small subset of Python. It can achieve C-like or FORTRAN-like speeds with this subset-- when tuned properly, it can automatically parallelize and automatically use the GPU. However, you won't really be writing your code in Python, but in Numba.
Alternatives
You can write performance-critical code in another programming language.
One to consider would be D, a modern programming language with excellent C compatibility.
Python integrates easily with languages C. In fact, you can load dynamic libraries written in C* into Python with no glue code.
*D should be able to do this with extern(C):
and -betterC
.
$endgroup$
Python compilers for performance
Nuitka
Nuitka compiles any and all Python code into faster architecture-specific C++ code. Nuitka's generated code is faster.
Cython
Cython can compiles any and all Python code into platform-indepndent C code. However, where it really shines is because you can annotate your Cython functions with C types and get a performance boost out of that.
PyPy
PyPy is a JIT that compiles pure Python code. Sometimes it can produce good code, but it has a slow startup time. Although PyPy probably won't give you C-like or FORTRAN-like speeds, it can sometimes double or triple the execution speed of performance-critical sections.
However, PyPy is low on developers, and as such, it does not yet support Python versions 3.7 or 3.8. Most libraries are still written with 3.6 compatibility.
Numba
Numba compiles a small subset of Python. It can achieve C-like or FORTRAN-like speeds with this subset-- when tuned properly, it can automatically parallelize and automatically use the GPU. However, you won't really be writing your code in Python, but in Numba.
Alternatives
You can write performance-critical code in another programming language.
One to consider would be D, a modern programming language with excellent C compatibility.
Python integrates easily with languages C. In fact, you can load dynamic libraries written in C* into Python with no glue code.
*D should be able to do this with extern(C):
and -betterC
.
edited Sep 19 at 23:36
answered Sep 19 at 23:28
noɥʇʎԀʎzɐɹƆnoɥʇʎԀʎzɐɹƆ
3571 silver badge13 bronze badges
3571 silver badge13 bronze badges
add a comment
|
add a comment
|
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f229282%2fperformance-for-simple-code-that-converts-a-rgb-tuple-to-hex-string%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
18
$begingroup$
It will need to be called once per pixel
- this is suspicious. What code is accepting a hex string? High-performance graphics code should be dealing in RGB byte triples packed into an int32.$endgroup$
– Reinderien
Sep 19 at 1:32
1
$begingroup$
Put another way: I think you need to approach this from another angle. Make a framebuffer using a lower-level technology and pull that into tkinter wholesale.
$endgroup$
– Reinderien
Sep 19 at 1:38
3
$begingroup$
@Reinderien Canvas pixel coloring seems to be done exclusively using hex strings in tkinter unfortunately. Unless I can have it accept a buffer of existing data. I'll look into that tomorrow. Thanks.
$endgroup$
– Carcigenicate
Sep 19 at 2:00
3
$begingroup$
And I'm going to remove the
number-guessing-game
tag. I'm not sure why that was added.$endgroup$
– Carcigenicate
Sep 19 at 2:02
1
$begingroup$
Where are you getting those tuples? Perhaps there's an easy way to stay out of python for longer and therefore optimize better. Just calling any function 1.5e9 times is going to be slower than you want. For example, if you get them from a long continues array of some kind, numpy makes this trivial, especially since you can probably just shove numpy's own byte buffer into tkinter. I've had an issue like this myself once for displaying an image, and the solution was "let numpy handle it."
$endgroup$
– Gloweye
Sep 19 at 8:25