Skip to content

Commit 7907fa6

Browse files
laramielcopybara-github
authored andcommitted
Add file_io_mode context flag
"file_io_mode" indicates the mode used to read files. It currently supports the values "default", "memmap" (replaces "file_io_memmap") and "direct" (linux only, experimental). When set to "direct", if tensorstore detects that the reads can be satisified using direct io, then it will convert the file to use O_DIRECT and issue those reads. Note that reads have to be aligned to and multiples of (typically) 4096 bytes, and this is only supported on relatively recent linux implementations. "file_io_memmap" has been removed in favor of "file_io_mode": "memmap". Otherwise this behaves the same. PiperOrigin-RevId: 786790774 Change-Id: Ibdb9ec9f8523a44d5d8ef647fa298db328b47851
1 parent 7624e88 commit 7907fa6

26 files changed

+606
-156
lines changed

python/tensorstore/kvstore.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ ensure there is a :python:`'/'` separator, use :py:obj:`.__truediv__` instead.
334334
'context': {
335335
'file_io_concurrency': {},
336336
'file_io_locking': {},
337-
'file_io_memmap': False,
337+
'file_io_mode': {},
338338
'file_io_sync': True,
339339
},
340340
'driver': 'file',
@@ -345,7 +345,7 @@ ensure there is a :python:`'/'` separator, use :py:obj:`.__truediv__` instead.
345345
'context': {
346346
'file_io_concurrency': {},
347347
'file_io_locking': {},
348-
'file_io_memmap': False,
348+
'file_io_mode': {},
349349
'file_io_sync': True,
350350
},
351351
'driver': 'file',
@@ -374,7 +374,7 @@ Returns a key-value store with an additional path component joined to the path.
374374
'context': {
375375
'file_io_concurrency': {},
376376
'file_io_locking': {},
377-
'file_io_memmap': False,
377+
'file_io_mode': {},
378378
'file_io_sync': True,
379379
},
380380
'driver': 'file',
@@ -385,7 +385,7 @@ Returns a key-value store with an additional path component joined to the path.
385385
'context': {
386386
'file_io_concurrency': {},
387387
'file_io_locking': {},
388-
'file_io_memmap': False,
388+
'file_io_mode': {},
389389
'file_io_sync': True,
390390
},
391391
'driver': 'file',
@@ -1174,7 +1174,7 @@ Returns a string representation based on the :json:schema:`JSON representation<
11741174
'context': {
11751175
'file_io_concurrency': {},
11761176
'file_io_locking': {},
1177-
'file_io_memmap': False,
1177+
'file_io_mode': {},
11781178
'file_io_sync': True,
11791179
},
11801180
'driver': 'file',
@@ -1196,7 +1196,7 @@ Returns a copy of the key-value store.
11961196
'context': {
11971197
'file_io_concurrency': {},
11981198
'file_io_locking': {},
1199-
'file_io_memmap': False,
1199+
'file_io_mode': {},
12001200
'file_io_sync': True,
12011201
},
12021202
'driver': 'file',
@@ -1207,7 +1207,7 @@ Returns a copy of the key-value store.
12071207
'context': {
12081208
'file_io_concurrency': {},
12091209
'file_io_locking': {},
1210-
'file_io_memmap': False,
1210+
'file_io_mode': {},
12111211
'file_io_sync': True,
12121212
},
12131213
'driver': 'file',
@@ -1418,7 +1418,7 @@ Converts to the :json:schema:`JSON representation<KvStore>`.
14181418
'driver': 'file',
14191419
'file_io_concurrency': 'file_io_concurrency',
14201420
'file_io_locking': 'file_io_locking',
1421-
'file_io_memmap': 'file_io_memmap',
1421+
'file_io_mode': 'file_io_mode',
14221422
'file_io_sync': 'file_io_sync',
14231423
'path': 'tmp/dataset/abc/'}
14241424

tensorstore/driver/auto/driver_test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ TEST(AutoTest, UnbindContextSimple) {
128128
{"cache_pool", {{"total_bytes_limit", 1000}}},
129129
{"file_io_concurrency", ::nlohmann::json::object_t()},
130130
{"file_io_locking", ::nlohmann::json::object_t()},
131-
{"file_io_memmap", false},
131+
{"file_io_mode", ::nlohmann::json::object_t()},
132132
{"file_io_sync", true},
133133
{"ocdbt_coordinator", ::nlohmann::json::object_t()},
134134
}}})));
@@ -165,7 +165,7 @@ TEST(AutoTest, UnbindContext1) {
165165
{"cache_pool", {{"total_bytes_limit", 1000}}},
166166
{"file_io_concurrency", ::nlohmann::json::object_t()},
167167
{"file_io_locking", ::nlohmann::json::object_t()},
168-
{"file_io_memmap", false},
168+
{"file_io_mode", ::nlohmann::json::object_t()},
169169
{"file_io_sync", true},
170170
{"ocdbt_coordinator", ::nlohmann::json::object_t()},
171171
}}})));

tensorstore/driver/auto/index.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ A :ref:`zarr3<driver/zarr3>` TensorStore can be detected from its path:
6767
'data_copy_concurrency': {},
6868
'file_io_concurrency': {},
6969
'file_io_locking': {},
70-
'file_io_memmap': False,
70+
'file_io_mode': {},
7171
'file_io_sync': True,
7272
},
7373
'driver': 'zarr3',
@@ -93,7 +93,7 @@ A :ref:`zarr3<driver/zarr3>` TensorStore can be detected from its path:
9393
'data_copy_concurrency': {},
9494
'file_io_concurrency': {},
9595
'file_io_locking': {},
96-
'file_io_memmap': False,
96+
'file_io_mode': {},
9797
'file_io_sync': True,
9898
},
9999
'driver': 'zarr3',
@@ -169,7 +169,7 @@ used in conjunction with format auto-detection:
169169
'data_copy_concurrency': {},
170170
'file_io_concurrency': {},
171171
'file_io_locking': {},
172-
'file_io_memmap': False,
172+
'file_io_mode': {},
173173
'file_io_sync': True,
174174
},
175175
'driver': 'cast',
@@ -196,7 +196,7 @@ from the path to the OCDBT database.
196196
'data_copy_concurrency': {},
197197
'file_io_concurrency': {},
198198
'file_io_locking': {},
199-
'file_io_memmap': False,
199+
'file_io_mode': {},
200200
'file_io_sync': True,
201201
'ocdbt_coordinator': {},
202202
},
@@ -233,7 +233,7 @@ from the path to the OCDBT database.
233233
'data_copy_concurrency': {},
234234
'file_io_concurrency': {},
235235
'file_io_locking': {},
236-
'file_io_memmap': False,
236+
'file_io_mode': {},
237237
'file_io_sync': True,
238238
'ocdbt_coordinator': {},
239239
},
@@ -278,7 +278,7 @@ of the OCDBT database:
278278
'data_copy_concurrency': {},
279279
'file_io_concurrency': {},
280280
'file_io_locking': {},
281-
'file_io_memmap': False,
281+
'file_io_mode': {},
282282
'file_io_sync': True,
283283
'ocdbt_coordinator': {},
284284
},

tensorstore/internal/os/BUILD

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,28 @@ _LINKOPTS = select({
1515
"//conditions:default": [],
1616
})
1717

18+
tensorstore_cc_library(
19+
name = "aligned_alloc",
20+
srcs = ["aligned_alloc.cc"],
21+
hdrs = ["aligned_alloc.h"],
22+
deps = [
23+
":include_windows",
24+
":memory_region",
25+
"@abseil-cpp//absl/log:absl_log",
26+
],
27+
)
28+
29+
tensorstore_cc_test(
30+
name = "aligned_alloc_test",
31+
srcs = ["aligned_alloc_test.cc"],
32+
deps = [
33+
":aligned_alloc",
34+
":memory_region",
35+
"@abseil-cpp//absl/strings:cord",
36+
"@googletest//:gtest_main",
37+
],
38+
)
39+
1840
tensorstore_cc_library(
1941
name = "cwd",
2042
srcs = ["cwd.cc"],
@@ -161,10 +183,12 @@ tensorstore_cc_test(
161183
name = "file_util_test",
162184
srcs = ["file_util_test.cc"],
163185
deps = [
186+
":aligned_alloc",
164187
":file_util",
165188
"//tensorstore/internal/testing:scoped_directory",
166189
"//tensorstore/util:span",
167190
"//tensorstore/util:status_testutil",
191+
"@abseil-cpp//absl/log:absl_log",
168192
"@abseil-cpp//absl/status",
169193
"@abseil-cpp//absl/strings",
170194
"@abseil-cpp//absl/strings:cord",
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2020 The TensorStore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "tensorstore/internal/os/aligned_alloc.h"
16+
17+
#include <stddef.h>
18+
#include <stdint.h>
19+
20+
#include <algorithm>
21+
#include <cassert>
22+
#include <cstdio>
23+
#include <cstdlib>
24+
25+
#include "absl/log/absl_log.h"
26+
#include "tensorstore/internal/os/memory_region.h"
27+
28+
// Include system headers last to reduce impact of macros.
29+
#include "tensorstore/internal/os/include_windows.h"
30+
31+
namespace tensorstore {
32+
namespace internal_os {
33+
namespace {
34+
35+
#ifdef _WIN32
36+
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc?view=msvc-170
37+
void AlignedFree(char* data, size_t size) { _aligned_free(data); }
38+
#else
39+
void AlignedFree(char* data, size_t size) { free(data); }
40+
#endif
41+
42+
} // namespace
43+
44+
MemoryRegion AllocatePageAlignedRegion(size_t alignment, size_t size) {
45+
if (size == 0) {
46+
return MemoryRegion(nullptr, 0, AlignedFree);
47+
}
48+
assert((alignment & (alignment - 1)) == 0);
49+
size_t rounded_size = 0;
50+
51+
#ifdef _WIN32
52+
void* p = _aligned_malloc(size, alignment);
53+
#elif defined(__APPLE__)
54+
// https://reviews.llvm.org/D138196
55+
// ::aligned_alloc requires macos 15.0 or later. etc.
56+
void* p = nullptr;
57+
::posix_memalign(&p, alignment, size);
58+
#else
59+
rounded_size = (size + alignment - 1) & ~(alignment - 1);
60+
void* p = ::aligned_alloc(alignment, std::max(size, rounded_size));
61+
#endif
62+
63+
if (p == nullptr) {
64+
ABSL_LOG(FATAL) << "Failed to allocate memory " << size;
65+
}
66+
return MemoryRegion(static_cast<char*>(p), std::max(size, rounded_size),
67+
AlignedFree);
68+
}
69+
70+
} // namespace internal_os
71+
} // namespace tensorstore
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2020 The TensorStore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef TENSORSTORE_INTERNAL_OS_ALIGNED_ALLOC_H_
16+
#define TENSORSTORE_INTERNAL_OS_ALIGNED_ALLOC_H_
17+
18+
#include <stddef.h>
19+
#include <stdint.h>
20+
21+
#include <cstdio>
22+
#include <cstdlib>
23+
24+
#include "tensorstore/internal/os/memory_region.h"
25+
26+
namespace tensorstore {
27+
namespace internal_os {
28+
29+
/// Try to allocate a region of memory backed the heap, page aligned.
30+
MemoryRegion AllocatePageAlignedRegion(size_t alignment, size_t size);
31+
32+
} // namespace internal_os
33+
} // namespace tensorstore
34+
35+
#endif // TENSORSTORE_INTERNAL_OS_ALIGNED_ALLOC_H_
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2025 The TensorStore Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "tensorstore/internal/os/aligned_alloc.h"
16+
17+
#include <stddef.h>
18+
19+
#include <utility>
20+
21+
#include <gmock/gmock.h>
22+
#include <gtest/gtest.h>
23+
#include "absl/strings/cord.h"
24+
#include "tensorstore/internal/os/memory_region.h"
25+
26+
using ::tensorstore::internal_os::AllocatePageAlignedRegion;
27+
28+
namespace {
29+
30+
TEST(AlignedAllocTest, AllocatePageAlignedRegion) {
31+
auto region = AllocatePageAlignedRegion(1024, 16 * 1024 * 1024);
32+
EXPECT_THAT(region.as_string_view().size(), testing::Ge(16 * 1024 * 1024));
33+
34+
// Verify that assignment doesn't leak.
35+
region = AllocatePageAlignedRegion(1024, 16);
36+
EXPECT_THAT(region.as_string_view().size(), testing::Ge(16));
37+
38+
absl::Cord a = std::move(region).as_cord();
39+
}
40+
41+
} // namespace

tensorstore/internal/os/file_descriptor.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,38 @@
1717

1818
#include "tensorstore/internal/os/unique_handle.h"
1919

20+
// Include system headers last to reduce impact of macros.
21+
#include "tensorstore/internal/os/include_windows.h"
22+
2023
namespace tensorstore {
2124
namespace internal_os {
2225

2326
#ifdef _WIN32
2427
// Specializations for Windows.
2528

2629
/// Representation of open file/directory.
27-
using FileDescriptor = void*; // HANDLE
30+
using FileDescriptor = HANDLE; // HANDLE
31+
32+
/// File descriptor traits for use with `UniqueHandle`.
33+
struct FileDescriptorTraits {
34+
static FileDescriptor Invalid() { return INVALID_HANDLE_VALUE; }
35+
static void Close(FileDescriptor fd);
36+
};
37+
2838
#else
2939
// Specializations for Posix.
3040

3141
/// Representation of open file/directory.
3242
using FileDescriptor = int;
33-
#endif
3443

3544
/// File descriptor traits for use with `UniqueHandle`.
3645
struct FileDescriptorTraits {
37-
static FileDescriptor Invalid() { return ((FileDescriptor)-1); }
46+
static FileDescriptor Invalid() { return -1; }
3847
static void Close(FileDescriptor fd);
3948
};
4049

50+
#endif
51+
4152
/// Unique handle to an open file descriptor.
4253
///
4354
/// The file descriptor is closed automatically by the destructor.

tensorstore/internal/os/file_util.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class MappedRegion;
6767
/// multiple of this value.
6868
uint32_t GetDefaultPageSize();
6969

70+
/// Returns the block alignment for Direct IO.
71+
size_t GetDirectIoBlockAlignment(FileDescriptor fd);
72+
7073
/// Returns a Cord containing the contents of the file.
7174
///
7275
/// \param fd File descriptor opened with `OpenFlags::OpenReadOnly`. The
@@ -87,6 +90,11 @@ enum class OpenFlags : int {
8790
Create = O_CREAT,
8891
Append = O_APPEND,
8992
Exclusive = O_EXCL,
93+
#if defined(O_DIRECT)
94+
Direct = O_DIRECT,
95+
#else
96+
Direct = 0x4000,
97+
#endif
9098
CloseOnExec = O_CLOEXEC,
9199
ReadWriteMask = O_RDONLY | O_WRONLY | O_RDWR,
92100

0 commit comments

Comments
 (0)