Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RColumn.hxx
Go to the documentation of this file.
1/// \file ROOT/RColumn.hxx
2/// \ingroup NTuple ROOT7
3/// \author Jakob Blomer <jblomer@cern.ch>
4/// \date 2018-10-09
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
16#ifndef ROOT7_RColumn
17#define ROOT7_RColumn
18
19#include <ROOT/RConfig.hxx> // for R__likely
21#include <ROOT/RNTupleUtil.hxx>
22#include <ROOT/RPage.hxx>
23#include <ROOT/RPageStorage.hxx>
24
25#include <TError.h>
26
27#include <cstring> // for memcpy
28#include <memory>
29#include <utility>
30
31namespace ROOT {
32namespace Experimental {
33namespace Internal {
34
35// clang-format off
36/**
37\class ROOT::Experimental::Internal::RColumn
38\ingroup NTuple
39\brief A column is a storage-backed array of a simple, fixed-size type, from which pages can be mapped into memory.
40*/
41// clang-format on
42class RColumn {
43private:
45 std::uint16_t fBitsOnStorage = 0;
46 /// Columns belonging to the same field are distinguished by their order. E.g. for an std::string field, there is
47 /// the offset column with index 0 and the character value column with index 1.
48 std::uint32_t fIndex;
49 /// Fields can have multiple column representations, distinguished by representation index
50 std::uint16_t fRepresentationIndex;
51 RPageSink *fPageSink = nullptr;
55 /// A set of open pages into which new elements are being written. The pages are used
56 /// in rotation. If tail page optimization is enabled, they are 50% bigger than the target size
57 /// given by the write options. The current page is filled until the target size, but it is only
58 /// committed once the other write page is filled at least 50%. If a flush occurs earlier, a
59 /// slightly oversized, single page will be committed.
60 /// Without tail page optimization, only one page is allocated equal to the target size.
62 /// Index of the current write page
64 /// For writing, the targeted number of elements, given by `fApproxNElementsPerPage` (in the write options) and the
65 /// element size. We ensure this value to be >= 2 in Connect() so that we have meaningful "page full" and "page half
66 /// full" events when writing the page.
67 std::uint32_t fApproxNElementsPerPage = 0;
68 /// The number of elements written resp. available in the column
70 /// The currently mapped page for reading
72 /// The column id is used to find matching pages with content when reading
74 /// Global index of the first element in this column; usually == 0, unless it is a deferred column
76 /// Used to pack and unpack pages on writing/reading
77 std::unique_ptr<RColumnElementBase> fElement;
78 /// The column team is a set of columns that serve the same column index for different representation IDs.
79 /// Initially, the team has only one member, the very column it belongs to. Through MergeTeams(), two columns
80 /// can join forces. The team is used to react on suppressed columns: if the current team member has a suppressed
81 /// column for a MapPage() call, it get the page from the active column in the corresponding cluster.
82 std::vector<RColumn *> fTeam;
83 /// Points into fTeam to the column that successfully returned the last page.
84 std::size_t fLastGoodTeamIdx = 0;
85
86 RColumn(EColumnType type, std::uint32_t columnIndex, std::uint16_t representationIndex);
87
88 /// Used in Append() and AppendV() to handle the case when the main page reached the target size.
89 /// If tail page optimization is enabled, switch the pages; the other page has been flushed when
90 /// the main page reached 50%.
91 /// Without tail page optimization, flush the current page to make room for future writes.
93 {
95 return;
96
97 auto otherIdx = 1 - fWritePageIdx; // == (fWritePageIdx + 1) % 2
98 if (fWritePage[otherIdx].IsNull()) {
99 // There is only this page; we have to flush it now to make room for future writes.
102 } else {
103 fWritePageIdx = otherIdx;
106 }
107 }
108
109 /// When the main write page surpasses the 50% fill level, the (full) shadow write page gets flushed
111 {
112 auto otherIdx = 1 - fWritePageIdx;
113 if (fWritePage[otherIdx].IsEmpty())
114 return;
116 // Mark the page as flushed; the rangeFirst is zero for now but will be reset to
117 // fNElements in SwapWritePagesIfFull() when the pages swap
118 fWritePage[otherIdx].Reset(0);
119 }
120
121public:
122 template <typename CppT>
123 static std::unique_ptr<RColumn> Create(EColumnType type, std::uint32_t columnIdx, std::uint16_t representationIdx)
124 {
125 auto column = std::unique_ptr<RColumn>(new RColumn(type, columnIdx, representationIdx));
126 column->fElement = RColumnElementBase::Generate<CppT>(type);
127 return column;
128 }
129
130 RColumn(const RColumn &) = delete;
131 RColumn &operator=(const RColumn &) = delete;
132 ~RColumn();
133
134 /// Connect the column to a page sink. `firstElementIndex` can be used to specify the first column element index
135 /// with backing storage for this column. On read back, elements before `firstElementIndex` will cause the zero page
136 /// to be mapped.
137 void ConnectPageSink(DescriptorId_t fieldId, RPageSink &pageSink, NTupleSize_t firstElementIndex = 0U);
138 /// Connect the column to a page source.
139 void ConnectPageSource(DescriptorId_t fieldId, RPageSource &pageSource);
140
141 void Append(const void *from)
142 {
143 void *dst = fWritePage[fWritePageIdx].GrowUnchecked(1);
144
147 }
148
149 std::memcpy(dst, from, fElement->GetSize());
150 fNElements++;
151
153 }
154
155 void AppendV(const void *from, std::size_t count)
156 {
157 // We might not have enough space in the current page. In this case, fall back to one by one filling.
159 // TODO(jblomer): use (fewer) calls to AppendV to write the data page-by-page
160 for (unsigned i = 0; i < count; ++i) {
161 Append(static_cast<const unsigned char *>(from) + fElement->GetSize() * i);
162 }
163 return;
164 }
165
166 // The check for flushing the shadow page is more complicated than for the Append() case
167 // because we don't necessarily fill up to exactly fApproxNElementsPerPage / 2 elements;
168 // we might instead jump over the 50% fill level.
169 // This check should be done before calling `RPage::GrowUnchecked()` as the latter affects the return value of
170 // `RPage::GetNElements()`.
174 }
175
176 void *dst = fWritePage[fWritePageIdx].GrowUnchecked(count);
177
178 std::memcpy(dst, from, fElement->GetSize() * count);
179 fNElements += count;
180
181 // Note that by the very first check in AppendV, we cannot have filled more than fApproxNElementsPerPage elements
183 }
184
185 void Read(const NTupleSize_t globalIndex, void *to)
186 {
187 if (!fReadPageRef.Get().Contains(globalIndex)) {
188 MapPage(globalIndex);
189 }
190 const auto elemSize = fElement->GetSize();
191 void *from = static_cast<unsigned char *>(fReadPageRef.Get().GetBuffer()) +
192 (globalIndex - fReadPageRef.Get().GetGlobalRangeFirst()) * elemSize;
193 std::memcpy(to, from, elemSize);
194 }
195
196 void Read(RClusterIndex clusterIndex, void *to)
197 {
198 if (!fReadPageRef.Get().Contains(clusterIndex)) {
199 MapPage(clusterIndex);
200 }
201 const auto elemSize = fElement->GetSize();
202 void *from = static_cast<unsigned char *>(fReadPageRef.Get().GetBuffer()) +
203 (clusterIndex.GetIndex() - fReadPageRef.Get().GetClusterRangeFirst()) * elemSize;
204 std::memcpy(to, from, elemSize);
205 }
206
207 void ReadV(const NTupleSize_t globalIndex, const ClusterSize_t::ValueType count, void *to)
208 {
209 if (!fReadPageRef.Get().Contains(globalIndex)) {
210 MapPage(globalIndex);
211 }
212 NTupleSize_t idxInPage = globalIndex - fReadPageRef.Get().GetGlobalRangeFirst();
213
214 const auto elemSize = fElement->GetSize();
215 const void *from = static_cast<unsigned char *>(fReadPageRef.Get().GetBuffer()) + idxInPage * elemSize;
216 if (globalIndex + count <= fReadPageRef.Get().GetGlobalRangeLast() + 1) {
217 std::memcpy(to, from, elemSize * count);
218 } else {
219 ClusterSize_t::ValueType nBatch = fReadPageRef.Get().GetNElements() - idxInPage;
220 std::memcpy(to, from, elemSize * nBatch);
221 auto tail = static_cast<unsigned char *>(to) + nBatch * elemSize;
222 ReadV(globalIndex + nBatch, count - nBatch, tail);
223 }
224 }
225
226 void ReadV(RClusterIndex clusterIndex, const ClusterSize_t::ValueType count, void *to)
227 {
228 if (!fReadPageRef.Get().Contains(clusterIndex)) {
229 MapPage(clusterIndex);
230 }
231 NTupleSize_t idxInPage = clusterIndex.GetIndex() - fReadPageRef.Get().GetClusterRangeFirst();
232
233 const auto elemSize = fElement->GetSize();
234 const void *from = static_cast<unsigned char *>(fReadPageRef.Get().GetBuffer()) + idxInPage * elemSize;
235 if (clusterIndex.GetIndex() + count <= fReadPageRef.Get().GetClusterRangeLast() + 1) {
236 std::memcpy(to, from, elemSize * count);
237 } else {
238 ClusterSize_t::ValueType nBatch = fReadPageRef.Get().GetNElements() - idxInPage;
239 std::memcpy(to, from, elemSize * nBatch);
240 auto tail = static_cast<unsigned char *>(to) + nBatch * elemSize;
241 ReadV(RClusterIndex(clusterIndex.GetClusterId(), clusterIndex.GetIndex() + nBatch), count - nBatch, tail);
242 }
243 }
244
245 template <typename CppT>
246 CppT *Map(const NTupleSize_t globalIndex)
247 {
248 NTupleSize_t nItems;
249 return MapV<CppT>(globalIndex, nItems);
250 }
251
252 template <typename CppT>
253 CppT *Map(RClusterIndex clusterIndex)
254 {
255 NTupleSize_t nItems;
256 return MapV<CppT>(clusterIndex, nItems);
257 }
258
259 template <typename CppT>
260 CppT *MapV(const NTupleSize_t globalIndex, NTupleSize_t &nItems)
261 {
262 if (R__unlikely(!fReadPageRef.Get().Contains(globalIndex))) {
263 MapPage(globalIndex);
264 }
265 // +1 to go from 0-based indexing to 1-based number of items
266 nItems = fReadPageRef.Get().GetGlobalRangeLast() - globalIndex + 1;
267 return reinterpret_cast<CppT *>(static_cast<unsigned char *>(fReadPageRef.Get().GetBuffer()) +
268 (globalIndex - fReadPageRef.Get().GetGlobalRangeFirst()) * sizeof(CppT));
269 }
270
271 template <typename CppT>
272 CppT *MapV(RClusterIndex clusterIndex, NTupleSize_t &nItems)
273 {
274 if (!fReadPageRef.Get().Contains(clusterIndex)) {
275 MapPage(clusterIndex);
276 }
277 // +1 to go from 0-based indexing to 1-based number of items
278 nItems = fReadPageRef.Get().GetClusterRangeLast() - clusterIndex.GetIndex() + 1;
279 return reinterpret_cast<CppT *>(static_cast<unsigned char *>(fReadPageRef.Get().GetBuffer()) +
280 (clusterIndex.GetIndex() - fReadPageRef.Get().GetClusterRangeFirst()) *
281 sizeof(CppT));
282 }
283
285 {
286 if (!fReadPageRef.Get().Contains(clusterIndex)) {
287 MapPage(clusterIndex);
288 }
289 return fReadPageRef.Get().GetClusterInfo().GetIndexOffset() + clusterIndex.GetIndex();
290 }
291
293 {
294 if (!fReadPageRef.Get().Contains(globalIndex)) {
295 MapPage(globalIndex);
296 }
298 globalIndex - fReadPageRef.Get().GetClusterInfo().GetIndexOffset());
299 }
300
301 /// For offset columns only, look at the two adjacent values that define a collection's coordinates
302 void GetCollectionInfo(const NTupleSize_t globalIndex, RClusterIndex *collectionStart, ClusterSize_t *collectionSize)
303 {
304 NTupleSize_t idxStart = 0;
305 NTupleSize_t idxEnd;
306 // Try to avoid jumping back to the previous page and jumping back to the previous cluster
307 if (R__likely(globalIndex > 0)) {
308 if (R__likely(fReadPageRef.Get().Contains(globalIndex - 1))) {
309 idxStart = *Map<ClusterSize_t>(globalIndex - 1);
310 idxEnd = *Map<ClusterSize_t>(globalIndex);
311 if (R__unlikely(fReadPageRef.Get().GetClusterInfo().GetIndexOffset() == globalIndex))
312 idxStart = 0;
313 } else {
314 idxEnd = *Map<ClusterSize_t>(globalIndex);
315 auto selfOffset = fReadPageRef.Get().GetClusterInfo().GetIndexOffset();
316 idxStart = (globalIndex == selfOffset) ? 0 : *Map<ClusterSize_t>(globalIndex - 1);
317 }
318 } else {
319 idxEnd = *Map<ClusterSize_t>(globalIndex);
320 }
321 *collectionSize = idxEnd - idxStart;
322 *collectionStart = RClusterIndex(fReadPageRef.Get().GetClusterInfo().GetId(), idxStart);
323 }
324
325 void GetCollectionInfo(RClusterIndex clusterIndex, RClusterIndex *collectionStart, ClusterSize_t *collectionSize)
326 {
327 auto index = clusterIndex.GetIndex();
328 auto idxStart = (index == 0) ? 0 : *Map<ClusterSize_t>(clusterIndex - 1);
329 auto idxEnd = *Map<ClusterSize_t>(clusterIndex);
330 *collectionSize = idxEnd - idxStart;
331 *collectionStart = RClusterIndex(clusterIndex.GetClusterId(), idxStart);
332 }
333
334 /// Get the currently active cluster id
335 void GetSwitchInfo(NTupleSize_t globalIndex, RClusterIndex *varIndex, std::uint32_t *tag)
336 {
337 auto varSwitch = Map<RColumnSwitch>(globalIndex);
338 *varIndex = RClusterIndex(fReadPageRef.Get().GetClusterInfo().GetId(), varSwitch->GetIndex());
339 *tag = varSwitch->GetTag();
340 }
341
342 void Flush();
343 void CommitSuppressed();
344
345 void MapPage(NTupleSize_t globalIndex) { R__ASSERT(TryMapPage(globalIndex)); }
346 void MapPage(RClusterIndex clusterIndex) { R__ASSERT(TryMapPage(clusterIndex)); }
347 bool TryMapPage(NTupleSize_t globalIndex);
348 bool TryMapPage(RClusterIndex clusterIndex);
349
350 bool ReadPageContains(NTupleSize_t globalIndex) const { return fReadPageRef.Get().Contains(globalIndex); }
351 bool ReadPageContains(RClusterIndex clusterIndex) const { return fReadPageRef.Get().Contains(clusterIndex); }
352
353 void MergeTeams(RColumn &other);
354
356 RColumnElementBase *GetElement() const { return fElement.get(); }
357 EColumnType GetType() const { return fType; }
358 std::uint16_t GetBitsOnStorage() const { return fBitsOnStorage; }
359 std::uint32_t GetIndex() const { return fIndex; }
360 std::uint16_t GetRepresentationIndex() const { return fRepresentationIndex; }
364 RPageSink *GetPageSink() const { return fPageSink; }
367}; // class RColumn
368
369} // namespace Internal
370} // namespace Experimental
371} // namespace ROOT
372
373#endif
#define R__likely(expr)
Definition RConfig.hxx:587
#define R__unlikely(expr)
Definition RConfig.hxx:586
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
A column element encapsulates the translation between basic C++ types and their column representation...
A column is a storage-backed array of a simple, fixed-size type, from which pages can be mapped into ...
Definition RColumn.hxx:42
RColumn(const RColumn &)=delete
std::size_t fLastGoodTeamIdx
Points into fTeam to the column that successfully returned the last page.
Definition RColumn.hxx:84
RPageStorage::ColumnHandle_t GetHandleSink() const
Definition RColumn.hxx:366
void Read(RClusterIndex clusterIndex, void *to)
Definition RColumn.hxx:196
void ConnectPageSink(DescriptorId_t fieldId, RPageSink &pageSink, NTupleSize_t firstElementIndex=0U)
Connect the column to a page sink.
Definition RColumn.cxx:44
std::unique_ptr< RColumnElementBase > fElement
Used to pack and unpack pages on writing/reading.
Definition RColumn.hxx:77
void HandleWritePageIfFull()
Used in Append() and AppendV() to handle the case when the main page reached the target size.
Definition RColumn.hxx:92
NTupleSize_t GetGlobalIndex(RClusterIndex clusterIndex)
Definition RColumn.hxx:284
RColumnElementBase * GetElement() const
Definition RColumn.hxx:356
RPageRef fReadPageRef
The currently mapped page for reading.
Definition RColumn.hxx:71
bool TryMapPage(NTupleSize_t globalIndex)
Definition RColumn.cxx:103
void MapPage(RClusterIndex clusterIndex)
Definition RColumn.hxx:346
void MapPage(NTupleSize_t globalIndex)
Definition RColumn.hxx:345
void GetCollectionInfo(RClusterIndex clusterIndex, RClusterIndex *collectionStart, ClusterSize_t *collectionSize)
Definition RColumn.hxx:325
ColumnId_t fColumnIdSource
The column id is used to find matching pages with content when reading.
Definition RColumn.hxx:73
void ReadV(RClusterIndex clusterIndex, const ClusterSize_t::ValueType count, void *to)
Definition RColumn.hxx:226
int fWritePageIdx
Index of the current write page.
Definition RColumn.hxx:63
CppT * Map(const NTupleSize_t globalIndex)
Definition RColumn.hxx:246
RPageSource * GetPageSource() const
Definition RColumn.hxx:363
static std::unique_ptr< RColumn > Create(EColumnType type, std::uint32_t columnIdx, std::uint16_t representationIdx)
Definition RColumn.hxx:123
void AppendV(const void *from, std::size_t count)
Definition RColumn.hxx:155
void Append(const void *from)
Definition RColumn.hxx:141
CppT * Map(RClusterIndex clusterIndex)
Definition RColumn.hxx:253
CppT * MapV(RClusterIndex clusterIndex, NTupleSize_t &nItems)
Definition RColumn.hxx:272
RColumn & operator=(const RColumn &)=delete
bool ReadPageContains(RClusterIndex clusterIndex) const
Definition RColumn.hxx:351
RPageStorage::ColumnHandle_t fHandleSource
Definition RColumn.hxx:54
NTupleSize_t fNElements
The number of elements written resp. available in the column.
Definition RColumn.hxx:69
std::uint16_t GetRepresentationIndex() const
Definition RColumn.hxx:360
void ReadV(const NTupleSize_t globalIndex, const ClusterSize_t::ValueType count, void *to)
Definition RColumn.hxx:207
std::vector< RColumn * > fTeam
The column team is a set of columns that serve the same column index for different representation IDs...
Definition RColumn.hxx:82
void Read(const NTupleSize_t globalIndex, void *to)
Definition RColumn.hxx:185
std::uint32_t fIndex
Columns belonging to the same field are distinguished by their order.
Definition RColumn.hxx:48
void ConnectPageSource(DescriptorId_t fieldId, RPageSource &pageSource)
Connect the column to a page source.
Definition RColumn.cxx:65
std::uint32_t fApproxNElementsPerPage
For writing, the targeted number of elements, given by fApproxNElementsPerPage (in the write options)...
Definition RColumn.hxx:67
void GetCollectionInfo(const NTupleSize_t globalIndex, RClusterIndex *collectionStart, ClusterSize_t *collectionSize)
For offset columns only, look at the two adjacent values that define a collection's coordinates.
Definition RColumn.hxx:302
CppT * MapV(const NTupleSize_t globalIndex, NTupleSize_t &nItems)
Definition RColumn.hxx:260
std::uint16_t fRepresentationIndex
Fields can have multiple column representations, distinguished by representation index.
Definition RColumn.hxx:50
void GetSwitchInfo(NTupleSize_t globalIndex, RClusterIndex *varIndex, std::uint32_t *tag)
Get the currently active cluster id.
Definition RColumn.hxx:335
RPageStorage::ColumnHandle_t fHandleSink
Definition RColumn.hxx:53
void FlushShadowWritePage()
When the main write page surpasses the 50% fill level, the (full) shadow write page gets flushed.
Definition RColumn.hxx:110
RPage fWritePage[2]
A set of open pages into which new elements are being written.
Definition RColumn.hxx:61
NTupleSize_t GetNElements() const
Definition RColumn.hxx:355
bool ReadPageContains(NTupleSize_t globalIndex) const
Definition RColumn.hxx:350
NTupleSize_t GetFirstElementIndex() const
Definition RColumn.hxx:362
RPageStorage::ColumnHandle_t GetHandleSource() const
Definition RColumn.hxx:365
RClusterIndex GetClusterIndex(NTupleSize_t globalIndex)
Definition RColumn.hxx:292
NTupleSize_t fFirstElementIndex
Global index of the first element in this column; usually == 0, unless it is a deferred column.
Definition RColumn.hxx:75
std::uint16_t GetBitsOnStorage() const
Definition RColumn.hxx:358
Reference to a page stored in the page pool.
Definition RPagePool.hxx:85
Abstract interface to write data into an ntuple.
virtual void CommitPage(ColumnHandle_t columnHandle, const RPage &page)=0
Write a page to the storage. The column must have been added before.
Abstract interface to read data from an ntuple.
A page is a slice of a column that is mapped into memory.
Definition RPage.hxx:46
void Reset(NTupleSize_t rangeFirst)
Forget all currently stored elements (size == 0) and set a new starting index.
Definition RPage.hxx:162
void * GrowUnchecked(ClusterSize_t::ValueType nElements)
Called during writing: returns a pointer after the last element and increases the element counter in ...
Definition RPage.hxx:151
ClusterSize_t::ValueType GetClusterRangeFirst() const
Definition RPage.hxx:128
NTupleSize_t GetGlobalRangeFirst() const
Definition RPage.hxx:126
const RClusterInfo & GetClusterInfo() const
Definition RPage.hxx:132
std::uint32_t GetNElements() const
Definition RPage.hxx:124
bool Contains(NTupleSize_t globalIndex) const
Definition RPage.hxx:134
ClusterSize_t::ValueType GetClusterRangeLast() const
Definition RPage.hxx:129
NTupleSize_t GetGlobalRangeLast() const
Definition RPage.hxx:127
Addresses a column element or field item relative to a particular cluster, instead of a global NTuple...
DescriptorId_t GetClusterId() const
ClusterSize_t::ValueType GetIndex() const
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
constexpr ColumnId_t kInvalidColumnId
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
std::int64_t ColumnId_t
Uniquely identifies a physical column within the scope of the current process, used to tag pages.
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
Wrap the integer in a struct in order to avoid template specialization clash with std::uint64_t.