BACKGROUND
I have a lot of numeric message codes in a NumPy array, and I'd need to convert them into strings fast. I have had some problems with the performance and would like to understand why and how to make it quick.
SOME BENCHMARKS
I - The trivial approach
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
The dictionary lookup takes the better part of my coffee break, 758 ms. (I also tried res = map(lookupdict.get, arr) but that's even worse.)
II - Without NumPy
import random
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = [ random.choice(lookupdict.keys()) for _ in range(1000000) ]
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
The timing results change quite considerably to 76 ms!
It should be noted that I am interested in timing the lookup. The random generation is just to create some test data. It is not interesting whether it takes a lot of time or not. All benchmark results given here are only for the one million lookups.
III - Convert NumPy array to a list
My first guess was that this has something to do with list vs. array problems. However, by modifying the NumPy version to use lists:
res = [ lookupdict[k] for k in list(arr) ]
gives me 778 ms, of which around 110 ms is spent converting the list and 570 ms doing the lookup. So, the lookup is a bit faster, but the total time is the same.
IV - Type conversion from np.int32 to int
As the only other difference seems to be the data type (np.int32 vs. int), I tried converting the types on-the-fly. This is a bit stupid, as probably dict does the same:
res = [ lookupdict[int(k)] for k in arr ]
However, this seems to do something interesting, because the time drops to 266 ms. It seems that almost-but-not-quite-the-same datatypes play nasty tricks with dictionary lookups and that dict code is not very efficient with conversions.
V - Dictionary key conversion to np.int32
To test this, I modified the NumPy version to use exactly the same data type in dict keys and lookup:
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
np.int32(1): "val1",
np.int32(2): "val2",
np.int32(27): "val3",
np.int32(35): "val4",
np.int32(59): "val5" }
# some test data
arr = np.random.choice(lookupdict.keys(), 1000000)
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
This improved to 177 ms. Not an insignificant improvement but a far cry form the 76 ms.
VI - Array conversion to use int objects
import numpy as np
# dictionary to use as the lookup dictionary
lookupdict = {
1: "val1",
2: "val2",
27: "val3",
35: "val4",
59: "val5" }
# some test data
arr = np.array([ random.choice(lookupdict.keys()) for _ in range(1000000) ],
dtype='object')
# create a list of words looked up
res = [ lookupdict[k] for k in arr ]
This gives 86 ms, which is already very close to the native Python 76 ms.
Result summary
- dict keys
int, indexing withint(native Python): 76 ms - dict keys
int, indexing withintobjects (NumPy): 86 ms - dict keys
np.int32, indexing withnp.int32: 177 ms - dict keys
int, indexing withnp.int32: 758 ms
QUESTION(S)
Why? And what can I do to make the dictionary lookups as fast as possible? My input data is a NumPy array, so the best (fastest but ugly) this far is to convert the dict keys into np.int32. (Unfortunately, the dict keys may be spread over a wide range of numbers, so indexing array-by-array is not a viable alternative. Fast it would be though, 10 ms.)