Fix tcmalloc compatibility issue with aligned_alloc#683
Fix tcmalloc compatibility issue with aligned_alloc#683wupq2022 wants to merge 1 commit intomicrosoft:cpp_mainfrom
Conversation
Replace aligned_alloc() with posix_memalign() to fix crashes when using tcmalloc. The issue occurs because aligned_alloc() uses glibc's allocator while tcmalloc replaces free(), causing "invalid pointer" errors when freeing memory. posix_memalign() is more compatible with tcmalloc and other memory allocators, as it properly integrates with the malloc/free interface that tcmalloc intercepts. Changes: - Add #include <stdlib.h> for posix_memalign() - Replace aligned_alloc() with posix_memalign() in alloc_aligned() - Add proper error handling for posix_memalign() return value This fixes crashes during index construction when InMemDataStore objects are destructed after saving the index.
There was a problem hiding this comment.
Pull request overview
This PR updates DiskANN’s aligned allocation helper to avoid crashes when running with tcmalloc by replacing aligned_alloc() with posix_memalign() on non-Windows platforms.
Changes:
- Include
<stdlib.h>to declareposix_memalign(). - Switch
alloc_aligned()(non-Windows) fromaligned_alloc()toposix_memalign(). - Add explicit error handling for the
posix_memalign()return code.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Use posix_memalign instead of aligned_alloc for tcmalloc compatibility. | ||
| // posix_memalign is more compatible with tcmalloc and avoids "invalid pointer" errors | ||
| // when freeing memory allocated with aligned_alloc and freed with tcmalloc's free(). | ||
| int ret = posix_memalign(ptr, align, size); | ||
| if (ret != 0) | ||
| { | ||
| *ptr = nullptr; |
There was a problem hiding this comment.
posix_memalign requires align to be a power-of-two and a multiple of sizeof(void*) (and typically >= sizeof(void*)). There are existing call sites that pass align = 1 (e.g., PQDataStore allocates _quantized_data with align=1), which will now fail with EINVAL and terminate. Consider special-casing small/invalid alignments (e.g., treat align < sizeof(void*) as sizeof(void*), or fall back to malloc when no special alignment is needed), and/or explicitly validate align and surface a clearer error than "Memory Allocation Failed" for EINVAL.
| // Use posix_memalign instead of aligned_alloc for tcmalloc compatibility. | |
| // posix_memalign is more compatible with tcmalloc and avoids "invalid pointer" errors | |
| // when freeing memory allocated with aligned_alloc and freed with tcmalloc's free(). | |
| int ret = posix_memalign(ptr, align, size); | |
| if (ret != 0) | |
| { | |
| *ptr = nullptr; | |
| // Normalize and validate alignment for posix_memalign. | |
| // posix_memalign requires that the alignment be a power of two and | |
| // a multiple of sizeof(void*), and typically at least sizeof(void*). | |
| size_t effective_align = align; | |
| if (effective_align < sizeof(void *)) | |
| effective_align = sizeof(void *); | |
| bool align_is_valid = (effective_align != 0) && | |
| ((effective_align & (effective_align - 1)) == 0) && | |
| (effective_align % sizeof(void *) == 0); | |
| if (!align_is_valid) | |
| { | |
| std::stringstream stream; | |
| stream << "Invalid alignment (" << align | |
| << ") requested for aligned allocation. Alignment must be a power-of-two " | |
| << "multiple of " << sizeof(void *) << "."; | |
| print_error_and_terminate(stream); | |
| } | |
| // Use posix_memalign instead of aligned_alloc for tcmalloc compatibility. | |
| // posix_memalign is more compatible with tcmalloc and avoids "invalid pointer" errors | |
| // when freeing memory allocated with aligned_alloc and freed with tcmalloc's free(). | |
| int ret = posix_memalign(ptr, effective_align, size); | |
| if (ret != 0) | |
| { | |
| *ptr = nullptr; | |
| if (ret == EINVAL) | |
| { | |
| std::stringstream stream; | |
| stream << "posix_memalign failed with EINVAL for alignment " << align | |
| << " (effective alignment " << effective_align | |
| << "). Alignment must be a power-of-two multiple of " << sizeof(void *) << "."; | |
| print_error_and_terminate(stream); | |
| } |
Replace aligned_alloc() with posix_memalign() to fix crashes when using tcmalloc. The issue occurs because aligned_alloc() uses glibc's allocator while tcmalloc replaces free(), causing "invalid pointer" errors when freeing memory.
posix_memalign() is more compatible with tcmalloc and other memory allocators, as it properly integrates with the malloc/free interface that tcmalloc intercepts.
Changes:
This fixes crashes during index construction when InMemDataStore objects are destructed after saving the index.
Reference Issues/PRs
Related to #527. This issue has been reported by multiple users experiencing the same "Attempt to free invalid pointer" error when using tcmalloc. This fix addresses the root cause of the compatibility issue between
aligned_alloc()and tcmalloc'sfree()function.What does this implement/fix? Briefly explain your changes.
This PR fixes a crash that occurs when building DiskANN indexes with tcmalloc enabled. The crash manifests as "Attempt to free invalid pointer" errors when
InMemDataStoreobjects are destructed after saving the index.Root Cause:
aligned_alloc()(C11) uses glibc's memory allocator directlyfree()functionfree()tries to free memory allocated byaligned_alloc(), it detects that the pointer was not allocated by tcmalloc and reports an "invalid pointer" errorSolution:
aligned_alloc()withposix_memalign()in thealloc_aligned()functionposix_memalign()is older (POSIX standard) and better supported by memory allocators like tcmallocposix_memalign()properly integrates with the malloc/free interface that tcmalloc interceptsposix_memalign()return value (it returns an error code instead of NULL)Changes:
#include <stdlib.h>forposix_memalign()functionaligned_alloc()call withposix_memalign()inalloc_aligned()functionAny other comments?
This change maintains backward compatibility as
posix_memalign()has been available since POSIX.1-2001 and is widely supported. The function signature and behavior are equivalent for the use case inalloc_aligned(), with the only difference being the return value (error code vs. NULL pointer).The fix has been tested and resolves the crash during index construction when using tcmalloc.