Python Mathematical Computation Performance Benchmark

The cover image was generated by Google Gemini.
While taking the graduate course Simulation Study, the instructor mentioned that the random numbers used in modern computers and programming are mostly not truly random in the strict sense, but rather pseudo-random numbers. Pseudo-random numbers are generated through mathematical algorithms that compute the next value based on the previous state. Therefore, as long as the algorithm, initial value (Seed), and current state are known, the entire random number sequence can theoretically be reproduced. This raises an interesting question: if every pseudo-random number can be calculated from the previous value, where does the very first number come from?
True Random Numbers and Pseudo-random Numbers
To answer this question, we first need to understand that random numbers can generally be divided into two categories: True Random Numbers and Pseudo-random Numbers.
True random numbers are generated from unpredictable physical phenomena in the real world, such as thermal noise produced by hardware circuits, clock jitter, mouse movement patterns, keyboard input intervals, disk I/O operations, and network packet events. Modern operating systems continuously collect these sources of information to build an entropy pool, which serves as the initial source for secure random number generators. Since these sources are difficult to predict, true random numbers provide higher security and are widely used in cryptographic applications, key exchange, HTTPS, SSH, digital signatures, and other security-related fields. However, collecting entropy sources and performing security processing introduce additional computational costs, making true random number generation generally slower.
In contrast, pseudo-random numbers are entirely based on mathematical operations. They are not directly generated from natural phenomena; instead, they use deterministic mathematical formulas to repeatedly calculate the next value from the previous state. As a result, they form sequences with good statistical properties. Although the entire sequence can be reproduced if the initial value and algorithm are known, making them less secure than true random numbers, their advantages include high computational speed, strong reproducibility, and minimal memory requirements for generating extremely long sequences. Therefore, pseudo-random numbers are widely used in Monte Carlo simulation, numerical analysis, statistical simulation, computer games, and general scientific computing.
Algorithms for Pseudo-random Number Generation
Returning to the original question, since pseudo-random numbers depend on the previous state, where does the first number come from? The answer is the initial value (Seed). If users manually specify a Seed, such as random.seed(42) in Python, the program will generate exactly the same random number sequence every time it is executed, which is useful for debugging and reproducing experimental results. If no Seed is specified, Python obtains an initial state with sufficient entropy from the operating system, usually derived from the entropy pool maintained by the operating system, and uses it as the starting point of the pseudo-random number generator.
In other words, modern computer random number generation does not rely entirely on true random numbers. Instead, it uses a small amount of true random information as the initial seed and then applies high-speed mathematical algorithms to efficiently generate a large number of pseudo-random numbers.
One of the most common pseudo-random number generators is the Linear Congruential Generator (LCG). It generates the next value using one multiplication, one addition, and one modulo operation. Its mathematical representation is:
$$ X_{n+1} = (a X_n + c) \bmod (m), $$
where $X_n$ represents the current state and $X_{n+1}$ represents the next state. Due to its simple structure, LCG has very high computational efficiency. However, inappropriate parameter selection may result in short periods or poor statistical properties. Therefore, many improved variants have been developed.
One important variant is the Lehmer Generator, which retains only the multiplication and modulo operations from the original LCG:
$$ X_{n+1} = a X_n \bmod (m). $$
In 1973, P. A. W. Lewis and G. P. Learmonth studied the Lehmer Generator and proposed a set of parameters with good statistical properties. Therefore, some literature refers to implementations using these parameters as the Lewis–Learmonth Generator. Later, in 1988, Park and Miller published the famous paper Random Number Generators: Good Ones Are Hard to Find, recommending the use of $a = 16807$ and $m = 2^{31} - 1 = 2147483647$ as the parameters for the Lehmer Generator. They referred to this implementation as the Minimal Standard Generator. Due to its good statistical properties, simple implementation, and high computational efficiency, it has become one of the most well-known implementations of the Lehmer Generator and has been widely used as a classic example in programming languages, numerical computing libraries, and textbooks.
On the other hand, Python’s standard library random module uses the Mersenne Twister (MT19937) algorithm by default. Compared with traditional LCG methods, Mersenne Twister provides a much longer period of $2^{19937}-1$ and superior statistical properties. Therefore, it can generate higher-quality pseudo-random numbers while maintaining high computational efficiency, making it one of the most widely used general-purpose pseudo-random number generators today.
From the above discussion, we can see that pseudo-random number generation is essentially the result of a sequence of mathematical operations, and the computational methods used by different algorithms directly affect their execution efficiency. In fact, not only random number generators, but also various common mathematical functions in Python, such as addition, subtraction, multiplication, division, exponentiation, logarithms, trigonometric functions, and various probability distributions, require different levels of mathematical computation internally.
Therefore, the performance differences among different operations become an interesting topic worth exploring. In the following sections, this article will use Python’s timeit module to benchmark various common mathematical operations and random number generation methods, and compare the execution time required for each operation in practice.
Python Mathematical Performance Benchmark
To fairly compare the execution efficiency of various operations, this experiment uses Python’s standard library module timeit for performance measurement. Compared with directly using time.time() or time.perf_counter() to record the execution time of a single run, timeit repeatedly executes the specified function a large number of times while minimizing the influence of background processes, system scheduling, and timing errors. Therefore, it is also the method officially recommended by Python for microbenchmarking.
In this experiment, each operation is wrapped as an independent function, and timeit.timeit() is used to execute each function repeatedly for one hundred million times ($10^8$ iterations). The total execution time required to complete all operations is then recorded.
The tested operations include not only basic arithmetic operations such as addition, subtraction, multiplication, division, and modulus, but also common mathematical functions including exponentiation, logarithms, square roots, trigonometric functions, inverse trigonometric functions, and hyperbolic functions.
Furthermore, to compare the efficiency of different pseudo-random number generation methods, several common probability distributions provided by Python’s random module, including Uniform, Normal, Gamma, Beta, and Triangular distributions, are included in the benchmark. In addition, Linear Congruential Generator (LCG) and Lewis–Learmonth Generator are implemented and tested, and their performance is compared with Python’s built-in random.random(), which uses the Mersenne Twister algorithm internally.
The following is the Python code used in this experiment.
| |
After executing the above program, the execution times of various mathematical operations and pseudo-random number generation methods can be obtained. Since each test was repeated $10^8$ times, the times shown in the table represent the accumulated total execution time when each operation is performed a large number of times, rather than the time required for a single operation.
To facilitate comparison of performance differences among different operations, this article also provides the Relative Time. The multiplication operation (Multiplication, *), which has the lowest execution time, is used as the baseline value of 1. The experimental results are shown in the following table:
| Operation | Execution Time (seconds) | Relative Time |
|---|---|---|
| Multiplication (*) | 7.879676 | 1.000 |
| Subtraction (-) | 7.922646 | 1.005 |
| Addition (+) | 8.003140 | 1.016 |
| Division (/) | 8.013074 | 1.017 |
| Modulus (%) | 8.823106 | 1.120 |
| Absolute (abs) | 8.688303 | 1.103 |
| Mersenne Twister (MT) | 10.562946 | 1.341 |
| Square Root (sqrt) | 12.481048 | 1.584 |
| Exponential (exp) | 13.003802 | 1.650 |
| Sine (sin) | 13.196323 | 1.675 |
| Cosine (cos) | 13.390240 | 1.699 |
| Logarithm Base 10 (log10) | 13.660472 | 1.734 |
| Arctangent (atan) | 13.710329 | 1.740 |
| Hyperbolic Sine (sinh) | 14.426154 | 1.831 |
| Hyperbolic Cosine (cosh) | 14.432388 | 1.832 |
| Square (**) | 14.748766 | 1.872 |
| Arccosine (acos) | 15.095459 | 1.916 |
| Hyperbolic Tangent (tanh) | 15.195617 | 1.928 |
| Inverse Hyperbolic Cosine (acosh) | 14.884797 | 1.889 |
| Inverse Hyperbolic Sine (asinh) | 15.559897 | 1.975 |
| Tangent (tan) | 15.903263 | 2.018 |
| Power (pow) | 16.376359 | 2.078 |
| Inverse Hyperbolic Tangent (atanh) | 16.708471 | 2.120 |
| Logarithm (log) | 17.360567 | 2.203 |
| Random Uniform | 28.061936 | 3.561 |
| Exponential Distribution | 37.672043 | 4.781 |
| Linear Lagged Generator (LLG) | 36.661224 | 4.653 |
| Linear Congruential Generator (LCG) | 40.522369 | 5.143 |
| Triangular Distribution | 54.295462 | 6.891 |
| Random Normal | 62.204152 | 7.894 |
| Random Integer | 72.215640 | 9.165 |
| Gamma Distribution | 141.519396 | 17.960 |
| Beta Distribution | 281.850351 | 35.769 |
From the experimental results, it can be observed that the execution times of the four basic arithmetic operations are very similar. Among them, multiplication has the fastest execution speed, requiring approximately 7.88 seconds to complete one hundred million calculations. Subtraction, addition, and division also take around 8 seconds, with only minor differences between them. This indicates that, in modern CPU architectures, basic arithmetic operations can usually be directly executed by hardware, resulting in very low computational costs.
For more complex mathematical functions, the execution times of square roots, exponentiation, logarithms, and trigonometric functions are significantly higher than those of basic arithmetic operations. The natural logarithm function (log) takes approximately 2.20 times longer than multiplication, while the power function (pow) requires approximately 2.08 times the execution time. For trigonometric functions, both sin() and cos() take around 13 seconds, whereas tan() requires approximately 15.9 seconds.
In addition to basic mathematical functions, this experiment also compares the performance of different random number generation methods. The results show that Python’s built-in Mersenne Twister (MT) requires only approximately 10.56 seconds, which is significantly faster than the LCG and LLG implementations written in Python. Although LCG and Lehmer Generator have very simple computational procedures from an algorithmic perspective, additional overhead from Python class method calls, object state updates, and modulo operations causes their actual execution speed to be lower than the highly optimized built-in random module.
For probability distribution functions, the results show that their execution times are generally higher than simply generating uniformly distributed random numbers. For example, random.uniform() requires approximately 28 seconds, while the Normal Distribution requires approximately 62 seconds. This is because many probability distributions do not directly generate random numbers; instead, they first generate basic uniformly distributed random numbers and then apply additional mathematical transformations to obtain the target distribution. As a result, their execution time increases significantly.
Among all tested probability distributions, the Beta Distribution has the highest execution time, requiring approximately 281.85 seconds, which is 35.77 times slower than the basic multiplication operation. This demonstrates that the computational costs of different mathematical functions can vary substantially. Even though these functions ultimately return only a single numerical value, the underlying algorithms used to generate them may have vastly different levels of complexity.
Environment
- Operating System: Windows 11 25H2
- Processor: 13th Gen Intel(R) Core(TM) i7-13700 (2.10 GHz)
- Memory: 32.0 GB
- Programming Language: Python 3.10.11
References
- novus. (April 26, 2014). 電腦的隨機數是如何做到的? 痞客邦. Retrieved July 10, 2026, from https://novus.pixnet.net/blog/posts/2032238099
- Linear congruential generator. (June 6, 2026). Wikipedia, The Free Encyclopedia. Retrieved July 10, 2026, from https://en.wikipedia.org/wiki/Linear_congruential_generator
- Lehmer random number generator. (October 6, 2025). Wikipedia, The Free Encyclopedia. Retrieved July 10, 2026, from https://en.wikipedia.org/wiki/Lehmer_random_number_generator


![[Thought] Historical Earthquake Locations Around Taiwan](https://Josh-test-lab.github.io/posts/Historical%20Earthquake%20Locations%20Around%20Taiwan/cover%20image.webp)






