Question: I am writing a helper class to evaluate a parametrized function over a grid. Since the grid does not change with the parameter, I chose to create it once and for all as a class attribute. However, I realized having the grid as a class attribute causes a significant performance drop as compared to having it as a global variable. Even more intriguing, the performance discrepancy noted for a 2D grid seems to disappear with a 1D one. Here is a minimum "working" example demonstrating the issue: import numpy as np import time grid_1d_size = 25000000 grid_2d_size = 5000 x_min, x_max = 0, np.pi / 2 y_min, y_max = -np.pi / 4, np.pi / 4 # Grid evaluation (2D) with grid as class attribute class GridEvaluation2DWithAttribute: def __init__(self): self.x_2d_values = np.linspace(x_min, x_max, grid_2d_size, dtype=np.float128) self.y_2d_values = np.linspace(y_min, y_max, grid_2d_size, dtype=np.float128) def grid_evaluate(self): cost_values = np.cos(self.x_2d_values[:, None] * self.y_2d_values[None, :]) return cost_values grid_eval_2d_attribute = GridEvaluation2DWithAttribute() initial_time = time.process_time() grid_eval_2d_attribute.grid_evaluate() final_time = time.process_time() print(f"2d grid, with grid as class attribute: {final_time - initial_time} seconds") # Grid evaluation (1D) with grid as global variable x_2d_values = np.linspace(x_min, x_max, grid_2d_size) y_2d_values = np.linspace(y_min, y_max, grid_2d_size) class GridEvaluation2DWithGlobal: def __init__(self): pass def grid_evaluate(self): cost_values = np.cos(x_2d_values[:, None] * y_2d_values[None, :]) return cost_values grid_eval_2d_global = GridEvaluation2DWithGlobal() initial_time = time.process_time() grid_eval_2d_global.grid_evaluate() final_time = time.process_time() print(f"2d grid, with grid as global variable: {final_time - initial_time} seconds") # Grid evaluation (1D) with grid as class attribute class GridEvaluation1DWithAttribute: def __init__(self): self.x_1d_values = np.linspace(x_min, x_max, grid_1d_size, dtype=np.float128) def grid_evaluate(self): cost_values = np.cos(self.x_1d_values) return cost_values grid_eval_1d_attribute = GridEvaluation1DWithAttribute() initial_time = time.process_time() grid_eval_1d_attribute.grid_evaluate() final_time = time.process_time() print(f"1d grid, with grid as class attribute: {final_time - initial_time} seconds") # Grid evaluation (1D) with grid as global variable x_1d_values = np.linspace(x_min, x_max, grid_1d_size, dtype=np.float128) class GridEvaluation1DWithGlobal: def __init__(self): pass def grid_evaluate(self): cost_values = np.cos(x_1d_values) return cost_values grid_eval_1d_global = GridEvaluation1DWithGlobal() initial_time = time.process_time() grid_eval_1d_global.grid_evaluate() final_time = time.process_time() print(f"1d grid, with grid as global variable: {final_time - initial_time} seconds")  And this is the output I get: 2d grid, with grid as class attribute: 0.8012442529999999 seconds 2d grid, with grid as global variable: 0.20206781899999982 seconds 1d grid, with grid as class attribute: 2.0631387639999996 seconds 1d grid, with grid as global variable: 2.136266148 seconds  How to explain this performance discrepancy? I moved the grid from a class attribute to a global variable. I expected this change to be neutral to performance. However, a significant change of performance resulted.

Answer: You aren't consistent with setting the datatype of your grid. Compare self.x_2d_values = np.linspace(x_min, x_max, grid_2d_size, dtype=np.float128) self.y_2d_values = np.linspace(y_min, y_max, grid_2d_size, dtype=np.float128)  With: x_2d_values = np.linspace(x_min, x_max, grid_2d_size) y_2d_values = np.linspace(y_min, y_max, grid_2d_size)  When not specified, the default type is np.float64, which is 2 times shorter and at least two times faster than np.float128, which matches the time you've measured.