Response times of simulated M/M/1 queue are not exponentially distributed

138 Views Asked by At

I have created a simulation for an M/M/1 queuing system in Python using Simpy.

Simulations

The code of the simulation is just one class triggering two processes (in the Simpy sense of process):

  • One for generating traffic.
  • The other for describing the server.
import simpy
from random import expovariate

class QueueSys:
    
    def __init__(self, env, lambd: float, mu: float):
        self.env = env
        
        self.__lambd = lambd # Traffic input rate (frequency)
        self.__mu = mu # Server processing rate (frequency)
        self.__counter = 0
        self.__queue = []
        self.__tracked_job_response_time = {} # Per each job, the times when they enter and leave the queue
        
        self.__server = env.process(self.__serve())
        self.__generator = env.process(self.__generate())

    def get_stats(self):
        result: list[float] = []
        for job_id, job_times in self.__tracked_job_response_time.items():
            t_start, t_end = job_times
            if t_start > 0 and t_end > 0:
                result.append(t_end - t_start)
        return result
    
    def __create_job(self):
        job_name = f"J{self.__counter}"
        self.__counter += 1
        return job_name
    
    def __enqueue(self, job: str):
        self.__queue.append(job)
        self.__track_job_start_time(job)
    
    def __pop_job(self) -> str | None:
        return self.__queue.pop(0) if len(self.__queue) > 0 else None
    
    def __complete_job(self, job: str):
        self.__track_job_end_time(job)
    
    def __get_timeout_delay(self, rate: float) -> float:
        return expovariate(rate)
    
    def __track_job_start_time(self, job: str):
        start_t, end_t = self.__tracked_job_response_time[job] if job in self.__tracked_job_response_time else (-1, -1)

        if start_t > 0:
            raise SystemError(f"Job '{job}' has already a tracked START time, unexpected")
        
        start_t = self.env.now
        self.__tracked_job_response_time[job] = (start_t, end_t)
    

    def __track_job_end_time(self, job: str):
        start_t, end_t = self.__tracked_job_response_time[job] if job in self.__tracked_job_response_time else (-1, -1)
        
        if end_t > 0:
            raise SystemError(f"Job '{job}' has already a tracked END time, unexpected")
        
        end_t = self.env.now
        self.__tracked_job_response_time[job] = (start_t, end_t)

    def __serve(self):
        while True:
            processed_job = self.__pop_job()
            yield self.env.timeout(self.__get_timeout_delay(self.__mu))

            if processed_job is not None:
                self.__complete_job(processed_job)
                print("processed job %s at %d - qlen: %d" % (processed_job, self.env.now, len(self.__queue)))
    
    def __generate(self):
        while True:
            new_job = self.__create_job()
            self.__enqueue(new_job)
            print("generated job '%s' at %d" % (new_job, self.env.now))

            yield self.env.timeout(self.__get_timeout_delay(self.__lambd))

Running and configuring the simulation

I run the simulation:

env = simpy.Environment()
queue_mm1 = QueueSys(env, 0.5, 0.9)

env.run(until=5000)

results = queue_mm1.get_stats()
  • Inter-arrivals are exponentially distributed with rate $\lambda$.
  • Serving times are exponentially distributed with rate $\mu$.

I make sure that $\lambda < \mu$ so that the queue is stable.

The results

When I plot the results:

import matplotlib.pyplot as plt

plt.xlabel("Response time ranges")
plt.ylabel("Frequency")
plt.title("Response times histogram")
plt.hist(results, 300, density=True, facecolor='m')
plt.show()

I get this distribution:

enter image description here

The problem

For an M/M/1 queue, the response time of the system should be:

$$ E[T] = (\mu - \lambda)^{-1} $$

So, theoretically, the mean response time should be: $E[T] = \frac{1}{0.4} = 2.5$.

The issue here is that I am expecting response times to be exponentially distributed with mean $\mu - \lambda$. However I get what seems to be a Poisson distribution.

And the mean of response times does not match either:

mean(results)

And get approx: $3.56$.

What am I doing wrong in my simulation?

Additional notes

If I plot the number of items in the system (I do this by sampling the system size, queue + server, at a regular cadence), then I get the correct distribution: Geometrical.

enter image description here

Note: I did not update the code for this part.

The average number of items in the system is theoretically:

$$ E[N] = \frac{\rho}{1-\rho} $$

So, theoretically, I should have $1.25$ items on average in the system, and that is the case fr the simulation when I get the mean number of customers.

So, the simulations are getting right the number of items in the queue, but something is wrong with response times.


Resources

You can find a publicly available runnable Jupyter notebook with this simulation at this Google Colab page.

1

There are 1 best solutions below

0
On

Indeed for an M/M/1 queue with $\lambda<\mu$, the steady state average delay is $\overline{W}=\frac{1}{\mu-\lambda}$ and the steady state delay distribution satisfies $P[W>x] = e^{-(\mu-\lambda)x}$ for all $x>0$.

If you have inter-arrival times $\{Y_k\}_{k=1}^{\infty}$ and service times $\{X_k\}_{k=1}^{\infty}$ then you can simulate a G/G/1 queue with delays $\{W_n\}_{n=1}^{\infty}$ given by $W_1=X_1$ and $$W_{n+1}= \max[W_n-Y_{n+1},0]+X_{n+1} \quad \forall n \in \{2, 3, 4, ...\}$$

For the special case of an M/M/1 queue with $\lambda=3, \mu=4$, it takes about $10^6$ arrivals to get a close match to theory, see Python simulation here: https://trinket.io/python3/9c74d2ebd6

Results for $\lambda=3,\mu=4$, $10^5$ iterations:

  • Empirical $E[W]$: 1.0293

  • Theory $E[W]$: 1.00

  • Empirical $P[W>1]$: 0.37761

  • Theory $P[W>1]$: 0.36787

Results for $\lambda=3, \mu=4, 10^6$ iterations:

  • Empirical $E[W]$: 1.003

  • Theory $E[W]$: 1.00

  • Empirical $P[W>1]$: 0.36788

  • Theory $P[W>1]$: 0.36787


Regarding what you wrote for Poisson distribution: If $Z$ is a random variable with a Poisson distribution, then $Z$ must take integer values with prob 1. So if your delays are always integers then you are not simulating correctly (since an M/M/1 queue has real-valued delays).