apache / apache/arrow

[C++] Reevaluating the purpose of `oversize_threshold:0` for jemalloc allocator

Open
#46,929 7 comments 0 reactions 0 assignees View on GitHub
Component: C++ Component: Other Component: Parquet Type: bug
Dominant language
C++
Stars
17.1k
Forks
4.3k
Avg merge
3d 13h
Merged PRs (30d)
88

Description

### Describe the bug, including details regarding any error messages, version, and platform.

jemalloc - 5.3.0
arrow - 19.0.1
OS - Debian GNU/Linux 11 (bullseye)

Hi, I was looking into a memory leak when calling `.to_pandas()` Similar to #44472, #45504 and more
Using the testing script from #45504, I was able to figure out there was no memory leak, but instead jemalloc was not reusing previously freed huge chunks.

I was able to narrow the source of the issue to `oversize_threshold:0` being set in src/arrow/memory_pool_jemalloc.cc.

Going through previous issues the only thing I found about the discussion on this topic was https://github.com/jemalloc/jemalloc/issues/1621

When running the benchmarks, I saw no significant difference, nor an increase of page faults.

- with jemalloc 5.3.0
```
BufferBuilderTinyWrites/real_time 360684428 ns 358642675 ns 2 bytes_per_second=709.762Mi/s
BufferBuilderSmallWrites/real_time 305835020 ns 303916528 ns 2 bytes_per_second=837.052Mi/s
BufferBuilderLargeWrites/real_time 289178465 ns 287547318 ns 2 bytes_per_second=880.533Mi/s
BuildBooleanArrayNoNulls 143933668 ns 143200075 ns 5 bytes_per_second=1.74581Gi/s items_per_second=234.319M/s
BuildIntArrayNoNulls 246846990 ns 245527002 ns 3 bytes_per_second=1.01822Gi/s items_per_second=136.663M/s
BuildAdaptiveIntNoNulls 59772705 ns 59395025 ns 11 bytes_per_second=4.20911Gi/s items_per_second=564.937M/s
```
- with jemalloc 5.3.0 and JE_ARROW_MALLOC_CONF="oversize_threshold:8388608" (the default value in jemalloc)
```
BufferBuilderTinyWrites/real_time 339901144 ns 338121634 ns 2 bytes_per_second=753.16Mi/s
BufferBuilderSmallWrites/real_time 272097971 ns 270577905 ns 2 bytes_per_second=940.837Mi/s
BufferBuilderLargeWrites/real_time 290057937 ns 288429017 ns 2 bytes_per_second=877.863Mi/s
BuildBooleanArrayNoNulls 134166656 ns 133502696 ns 5 bytes_per_second=1.87262Gi/s items_per_second=251.339M/s
BuildIntArrayNoNulls 236320010 ns 235106338 ns 3 bytes_per_second=1.06335Gi/s items_per_second=142.72M/s
BuildAdaptiveIntNoNulls 59095763 ns 58693504 ns 12 bytes_per_second=4.25942Gi/s items_per_second=571.689M/s
```

### Reproduction script
```py
# make_df.py
import pandas as pd
import numpy as np
import random
import string

np.random.seed(42)
random.seed(42)

def random_string(length):
letters = string.ascii_letters
return ''.join(random.choice(letters) for _ in range(length))

num_rows = 10**6
col_types = {
'col1': lambda: random_string(10),
'col2': lambda: np.random.randint(0, 1000),
'col3': lambda: np.random.random(),
'col4': lambda: random_string(5),
'col5': lambda: np.random.randint(1000, 10000),
'col6': lambda: np.random.uniform(0, 100),
'col7': lambda: random_string(8),
'col8': lambda: np.random.random() * 1000,
'col9': lambda: np.random.randint(0, 2),
'col10': lambda: random_string(1000)
}

data = {col: [func() for _ in range(num_rows)] for col, func in col_types.items()}
df = pd.DataFrame(data)
df.to_parquet('random_dataset.parquet', index=True)
'''
ls -lah random_dataset.parquet
-rw-r--r-- 1 user users 1018M Jun 23 18:44 random_dataset.parquet
'''
```

```py
# test_mem.py

import pyarrow as pa
import pyarrow.parquet as pq
import ctypes
import psutil
import time

import ctypes
libarrow = ctypes.CDLL('libarrow.so.1900')

def arrow__jemalloc_stats_print(opt):
libarrow._FuncPtr(('_ZN5arrow20jemalloc_stats_printEPKc', libarrow))(opt)

def print_memory_usage():
process = psutil.Process()
mem_info = process.memory_info()
print(f"Memory Usage: {mem_info.rss / 1024 / 1024:.2f} MB")
print(f"Virtual Memory Usage: {mem_info.vms / 1024 / 1024:.2f} MB")

options = {
'use_threads': True,
}

def mem_and_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()

# Print results
print_memory_usage()
print(f"Execution Time: {end_time - start_time:.6f} seconds")
return result
return wrapper

@mem_and_time
def test_func_pandas():
data_path = 'random_dataset.parquet'
df = pq.read_pandas(data_path).to_pandas(**options)
return None

if __name__ == "__main__":
for _ in range(7):
test_func_pandas()
```

When enabling the oversize_threshold, we see RSS and VMS dramatically decrease

- arrow 19.0.1 with jemalloc 5.3.0
```
Memory Usage: 1386.48 MB
Virtual Memory Usage: 9565.53 MB
Execution Time: 6.337768 seconds
Memory Usage: 1472.54 MB
Virtual Memory Usage: 13915.53 MB
Execution Time: 6.023845 seconds
Memory Usage: 1410.41 MB
Virtual Memory Usage: 17956.53 MB
Execution Time: 6.015289 seconds
Memory Usage: 1381.80 MB
Virtual Memory Usage: 20837.53 MB
Execution Time: 5.979996 seconds
Memory Usage: 1483.04 MB
Virtual Memory Usage: 21138.53 MB
Execution Time: 6.386113 seconds
Memory Usage: 1465.61 MB
Virtual Memory Usage: 21629.53 MB
Execution Time: 6.239962 seconds
Memory Usage: 1781.36 MB
Virtual Memory Usage: 22054.53 MB
Execution Time: 6.281846 seconds
```
- arrow 19.0.1 with jemalloc 5.3.0 with JE_ARROW_MALLOC_CONF="oversize_threshold:8388608"
```
Memory Usage: 172.74 MB
Virtual Memory Usage: 7949.03 MB
Execution Time: 5.929280 seconds
Memory Usage: 178.59 MB
Virtual Memory Usage: 8332.03 MB
Execution Time: 5.669349 seconds
Memory Usage: 176.27 MB
Virtual Memory Usage: 8489.03 MB
Execution Time: 5.410734 seconds
Memory Usage: 177.28 MB
Virtual Memory Usage: 8913.53 MB
Execution Time: 5.454942 seconds
Memory Usage: 173.00 MB
Virtual Memory Usage: 9267.53 MB
Execution Time: 6.096917 seconds
Memory Usage: 436.12 MB
Virtual Memory Usage: 9587.53 MB
Execution Time: 5.802535 seconds
Memory Usage: 185.91 MB
Virtual Memory Usage: 9347.53 MB
Execution Time: 5.888606 seconds
```

## Conclusion

I wanted to ask the maintainers if there is a particular reason why "oversize_threshold:0" is still set and a sanity check to see if there still is a performance hit when setting "oversize_threshold:8388608".

### Component(s)

C++, Parquet, Other

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.