Repository: piskvorky/gensim

Issue:
random.RandomState with different versions of numpy has vastly different performance
the performance of random.RandomState in word2vec.py (version 3.8.0)

```
def seeded_vector(self, seed_string, vector_size):
         once = random.RandomState(self.hashfxn(seed_string) & 0xffffffff)
         return (once.rand(vector_size) - 0.5) / vector_size
```

seemingly depends greatly on the version of numpy installed. With numpy = 1.14.3, the following code 

```
from  numpy.random import RandomState as Ran
from time import time
t1 = time()
for i in range(100000):
    temp = Ran(hash((i)) & 0xffffffff)
t2 = time()
t2-t1 
```


produced 
`0.28105926513671875`
exactly the same code with numpy= 1.18.1 produced 

`18.590345859527588`

I noticed this because I was training a model with millions of words as vocabulary, and after updating numpy unwittingly (via a anaconda update), I noticed that the time for build_vocab was significantly longer, and after some debugging, I nailed it down to random.RandomState in the `seeded_vector` function. 
I know this is indeed a numpy issue, but even they mentioned it that RandomState is legacy (https://docs.scipy.org/doc/numpy/reference/random/performance.html). Therefore I wonder if you have some plans to upgrade randomstate? Thanks! 


Discussion:
Thanks for reporting. What was the difference in `random.RandomState` that you nailed it down to?

We should replace `RandomState` if it's deprecated. What should it be replaced with, is the change 100% backward compatible? Can you open a PR?
what I found is that `numpy.random.RandomState` defaults to the legacy random generator, i.e., the slowest, however, there are other options: PCG64, SFC64, etc. You could simply use them (after `import numpy.random as random`) with, e.g., `random.SFC64`, instead of `random.RandomState`.Same code as above but using SFC64, even with numpy= 1.18.1, would give 
`2.767242193222046`
which is the fastest of the bunch, although still not as fast as in numpy=1.14.3
I don't know exactly what caused the performance change inside numpy, and I also don't know how you can easily amend the code without breaking compatibility. 
I don't think it's necessary (or reasonable for users to expect) that the same seeding initializes vectors identically across changed gensim versions. (The starting state shouldn't be particularly important for end results under realistic uses – such as multithreaded training, which introduces lots more uncontrollable randomness.) 

So we should just choose whatever `numpy` recommends as a fast pseudorandom generator. 
Agreed. A 60x slowdown is really worrying.

Resolving patch:
diff --git a/gensim/models/word2vec.py b/gensim/models/word2vec.py
--- a/gensim/models/word2vec.py
+++ b/gensim/models/word2vec.py
@@ -1691,8 +1691,8 @@ def prepare_weights(self, hs, negative, wv, update=False, vocabulary=None):
     def seeded_vector(self, seed_string, vector_size):
         """Get a random vector (but deterministic by seed_string)."""
         # Note: built-in hash() may vary by Python version or even (in Py3.x) per launch
-        once = random.RandomState(self.hashfxn(seed_string) & 0xffffffff)
-        return (once.rand(vector_size) - 0.5) / vector_size
+        once = random.Generator(random.SFC64(self.hashfxn(seed_string) & 0xffffffff))
+        return (once.random(vector_size) - 0.5) / vector_size
 
     def reset_weights(self, hs, negative, wv):
         """Reset all projection weights to an initial (untrained) state, but keep the existing vocabulary."""


Test patch: