Commit fca2bd43 authored by Philipp Götze's avatar Philipp Götze

Added custom Bitmap for individual handling of underlying words

parent fb6ffc73
......@@ -86,7 +86,7 @@ static void BM_TreeErase(benchmark::State &state) {
pop.drain();
const auto pos = treeRef.lookupPositionInLeafNode(leaf, KEYPOS);
// const auto pos = dbis::BitOperations::getFreeZero(leaf->bits.get_ro());
// const auto pos = leaf->bits.get_ro().getFreeZero();
#ifdef ENABLE_PCM
SocketCounterState before_sstate;
......
......@@ -93,7 +93,7 @@ static void BM_TreeInsert(benchmark::State &state) {
const auto reqTup = MyTuple(KEYPOS, KEYPOS * 100, KEYPOS * 1.0);
const auto pos = treeRef.lookupPositionInLeafNode(leaf, KEYPOS);
//const auto pos = dbis::BitOperations::getFreeZero(leaf->bits.get_ro());
// const auto pos = leaf->bits.get_ro().getFreeZero();
#ifdef ENABLE_PCM
SocketCounterState before_sstate;
......
This diff is collapsed.
This diff is collapsed.
......@@ -19,7 +19,6 @@
#define DBIS_FPTree_hpp_
#include <array>
#include <bitset>
#include <cmath>
#include <iostream>
......@@ -31,7 +30,7 @@
#include <libpmemobj++/utils.hpp>
#include "config.h"
#include "utils/BitOperations.hpp"
#include "utils/Bitmap.hpp"
#include "utils/PersistEmulation.hpp"
#include "utils/SearchFunctions.hpp"
......@@ -131,7 +130,7 @@ class FPTree {
static constexpr auto PaddingSize = (64 - SearchSize % 64) % 64;
// p<LeafSearch> search; ///< helper structure for faster searches
p<std::bitset<M>> bits; ///< bitset for valid entries
p<dbis::Bitmap<M>> bits; ///< bitmap for valid entries
p<std::array<uint8_t, M>> fp; ///< fingerprint array (n & 0xFF)
pptr<LeafNode> nextLeaf; ///< pointer to the subsequent sibling
pptr<LeafNode> prevLeaf; ///< pointer to the preceeding sibling
......@@ -501,7 +500,7 @@ class FPTree {
nodeRef.values.get_rw()[pos] = val;
return false;
}
pos = BitOperations::getFreeZero(nodeRef.bits.get_ro());
pos = nodeRef.bits.get_ro().getFreeZero();
if (pos == M) {
/* split the node */
splitLeafNode(node, splitInfo);
......@@ -511,14 +510,14 @@ class FPTree {
/* insert the new entry */
if (key > splitRef.key) {
insertInLeafNodeAtPosition(sibling, BitOperations::getFreeZero(sibRef.bits.get_ro()), key, val);
insertInLeafNodeAtPosition(sibling, sibRef.bits.get_ro().getFreeZero(), key, val);
} else {
if (key > nodeRef.keys.get_ro()[findMaxKeyPos(nodeRef.keys.get_ro(), nodeRef.bits.get_ro())]) {
/// Special case: new key would be the middle, thus must be right
insertInLeafNodeAtPosition(sibling, BitOperations::getFreeZero(sibRef.bits.get_ro()), key, val);
insertInLeafNodeAtPosition(sibling, sibRef.bits.get_ro().getFreeZero(), key, val);
splitRef.key = key;
} else {
insertInLeafNodeAtPosition(node, BitOperations::getFreeZero(nodeRef.bits.get_ro()), key, val);
insertInLeafNodeAtPosition(node, nodeRef.bits.get_ro().getFreeZero(), key, val);
}
}
/* inform the caller about the split */
......@@ -1012,7 +1011,7 @@ class FPTree {
/// move to a node with larger keys
for (auto i = 0u; i < toMove; ++i) {
const auto max = findMaxKeyPos(donorKeys, donorBits);
const auto pos = BitOperations::getFreeZero(receiverBits);
const auto pos = receiverBits.getFreeZero();
receiverBits.set(pos);
receiverHashs[pos] = fpHash(donorKeys[max]);
receiverKeys[pos] = donorKeys[max];
......@@ -1023,7 +1022,7 @@ class FPTree {
/// move to a node with smaller keys
for (auto i = 0u; i < toMove; ++i) {
const auto min = findMinKeyPos(donorKeys, donorBits);
const auto pos = BitOperations::getFreeZero(receiverBits);
const auto pos = receiverBits.getFreeZero();
receiverBits.set(pos);
receiverHashs[pos] = fpHash(donorKeys[min]);
receiverKeys[pos] = donorKeys[min];
......@@ -1132,7 +1131,7 @@ class FPTree {
const auto &node2Values = node2Ref.values.get_ro();
for (auto i = 0u; i < M; i++) {
if (node2Bits.test(i)) {
const auto pos = BitOperations::getFreeZero(node1Bits);
const auto pos = node1Bits.getFreeZero();
node1Bits.set(pos);
node1Hashs[pos] = node2Hashs[i];
node1Keys[pos] = node2Keys[i];
......
This diff is collapsed.
......@@ -18,12 +18,11 @@
#ifndef DBIS_wBPTree_hpp_
#define DBIS_wBPTree_hpp_
#include <libpmemobj/ctl.h>
#include <array>
#include <bitset>
#include <cmath>
#include <iostream>
#include <libpmemobj/ctl.h>
#include <libpmemobj++/make_persistent.hpp>
#include <libpmemobj++/p.hpp>
#include <libpmemobj++/persistent_ptr.hpp>
......@@ -31,7 +30,7 @@
#include <libpmemobj++/utils.hpp>
#include "config.h"
#include "utils/BitOperations.hpp"
#include "utils/Bitmap.hpp"
#include "utils/PersistEmulation.hpp"
#include "utils/SearchFunctions.hpp"
......@@ -122,12 +121,6 @@ class wHBPTree {
};
};
template<size_t E>
struct alignas(64) Search {
std::array<uint8_t, E+1> slot; ///< slot array for indirection, first = num
std::bitset<E> b; ///< bitset for valid entries
};
/**
* A structure for representing a leaf node of a B+ tree.
*/
......@@ -143,7 +136,7 @@ class wHBPTree {
static constexpr auto PaddingSize = (64 - SearchSize % 64) % 64;
p<std::array<uint8_t, M + 1>> slot; ///< slot array for indirection, first = num
p<std::bitset<M>> bits; ///< bitset for valid entries
p<dbis::Bitmap<M>> bits; ///< bitmap for valid entries
pptr<LeafNode> nextLeaf; ///< pointer to the subsequent sibling
pptr<LeafNode> prevLeaf; ///< pointer to the preceeding sibling
char padding[PaddingSize]; ///< padding to align keys to 64 bytes
......@@ -166,7 +159,7 @@ class wHBPTree {
static constexpr auto PaddingSize = (64 - SearchSize % 64) % 64;
std::array<uint8_t, N + 1> slot; ///< slot array for indirection, first = num
std::bitset<N> bits; ///< bitset for valid entries
dbis::Bitmap<N> bits; ///< bitmap for valid entries
char padding[PaddingSize]; ///< padding to align keys to 64 bytes
std::array<KeyType, N> keys; ///< the actual keys
std::array<Node, N + 1> children; ///< pointers to child nodes (BranchNode or LeafNode)
......@@ -634,7 +627,7 @@ class wHBPTree {
auto &nodeRef = *node;
auto &slots = nodeRef.slot.get_rw();
auto &bits = nodeRef.bits.get_rw();
const auto u = BitOperations::getFreeZero(bits); ///< unused Entry
const auto u = bits.getFreeZero(); ///< unused Entry
/* insert the new entry at unused position */
nodeRef.keys.get_rw()[u] = key;
......@@ -692,7 +685,7 @@ class wHBPTree {
}
/// Insert new key and children
auto &hostRef = *host;
const auto u = BitOperations::getFreeZero(hostRef.bits);
const auto u = hostRef.bits.getFreeZero();
hostRef.keys[u] = childSplitInfo.key;
hostRef.children[u] = childSplitInfo.leftChild;
......@@ -1051,7 +1044,7 @@ class wHBPTree {
receiverSlots[i + toMove] = receiverSlots[i];
/// move from donor to receiver
for (i = balancedNum + 1; i <= donorSlots[0]; i++, ++j) {
const auto u = BitOperations::getFreeZero(receiverBits);
const auto u = receiverBits.getFreeZero();
receiverKeys[u] = donorKeys[donorSlots[i]];
receiverValues[u] = donorValues[donorSlots[i]];
receiverSlots[j] = u;
......@@ -1061,7 +1054,7 @@ class wHBPTree {
} else {
/// move to a node with smaller keys
for (auto i = 1u; i < toMove + 1; ++i) {
const auto u = BitOperations::getFreeZero(receiverBits);
const auto u = receiverBits.getFreeZero();
receiverKeys[u] = donorKeys[donorSlots[i]];
receiverValues[u] = donorValues[donorSlots[i]];
receiverSlots[receiverSlots[0] + i] = u;
......@@ -1106,14 +1099,14 @@ class wHBPTree {
}
/// 1.2. move toMove keys/children from donor to receiver
/// the most right child first
const auto u = BitOperations::getFreeZero(receiverRef.bits);
const auto u = receiverRef.bits.getFreeZero();
receiverRef.keys[u] = parent->keys[parent->slot[pos]];
receiverRef.children[u] = donorRef.children[N];
receiverRef.slot[toMove] = u;
receiverRef.bits.set(u);
/// now the rest
for (auto i = 2u; i <= toMove; ++i) {
const auto u2 = BitOperations::getFreeZero(receiverRef.bits);
const auto u2 = receiverRef.bits.getFreeZero();
const auto dPos = donorRef.slot[balancedNum + i];
receiverRef.keys[u2] = donorRef.keys[dPos];
receiverRef.children[u2] = donorRef.children[dPos];
......@@ -1128,7 +1121,7 @@ class wHBPTree {
/// 2. move from one node to a node with smaller keys
else {
/// 2.1. copy parent key and rightmost child of receiver
const auto u = BitOperations::getFreeZero(receiverRef.bits);
const auto u = receiverRef.bits.getFreeZero();
receiverRef.keys[u] = parent->keys[parent->slot[pos]];
receiverRef.children[u] = receiverRef.children[N];
receiverRef.slot[receiverRef.slot[0] + 1] = u;
......@@ -1136,7 +1129,7 @@ class wHBPTree {
/// 2.2. move toMove keys/children from donor to receiver
for (auto i = 2u; i <= toMove; ++i) {
const auto u2 = BitOperations::getFreeZero(receiverRef.bits);
const auto u2 = receiverRef.bits.getFreeZero();
const auto dPos = donorRef.slot[i - 1];
receiverRef.keys[u2] = donorRef.keys[dPos];
receiverRef.children[u2] = donorRef.children[dPos];
......@@ -1181,7 +1174,7 @@ class wHBPTree {
/// we move all keys/values from node2 to node1
for (auto i = 1u; i < node2Slots[0] + 1; ++i) {
const auto u = BitOperations::getFreeZero(node1Bits);
const auto u = node1Bits.getFreeZero();
node1Keys[u] = node2Keys[node2Slots[i]];
node1Vals[u] = node2Vals[node2Slots[i]];
node1Slots[node1Slots[0] + i] = u;
......@@ -1213,7 +1206,7 @@ class wHBPTree {
assert(sibRef.keys[sibRef.slot[sNumKeys]] < key);
/// merge parent key and drag rightmost child forward
auto u = BitOperations::getFreeZero(sibRef.bits);
auto u = sibRef.bits.getFreeZero();
sibRef.keys[u] = key;
sibRef.children[u] = sibRef.children[N];
sibRef.bits.set(u);
......@@ -1221,7 +1214,7 @@ class wHBPTree {
/// merge node
for (auto i = 1u; i < nodeRef.slot[0] + 1; ++i) {
u = BitOperations::getFreeZero(sibRef.bits);
u = sibRef.bits.getFreeZero();
sibRef.keys[u] = nodeRef.keys[nodeRef.slot[i]];
sibRef.children[u] = nodeRef.children[nodeRef.slot[i]];
sibRef.bits.set(u);
......
......@@ -18,13 +18,12 @@
#ifndef WOP_SKIPLIST_HPP
#define WOP_SKIPLIST_HPP
#include <libpmemobj/ctl.h>
#include <algorithm> ///< std::copy
#include <array>
#include <bitset>
#include <cstdlib> ///< size_t
#include <iostream> ///< std:cout
#include <libpmemobj/ctl.h>
#include <libpmemobj++/container/array.hpp>
#include <libpmemobj++/make_persistent.hpp>
#include <libpmemobj++/p.hpp>
......@@ -32,7 +31,7 @@
#include <libpmemobj++/transaction.hpp>
#include <libpmemobj++/utils.hpp>
#include "utils/BitOperations.hpp"
#include "utils/Bitmap.hpp"
#include "utils/Random.hpp"
#include "utils/SearchFunctions.hpp"
......@@ -79,7 +78,7 @@ class woPSkiplist {
static constexpr auto PaddingSize = (64 - (BitsetSize + ForwardSize + 2 * sizeof(KeyType) +
sizeof(size_t)) % 64) % 64;
p<std::bitset<N>> bitset; ///< a bitmap indicating empty/used slots
p<dbis::Bitmap<N>> bits; ///< a bitmap indicating empty/used slots
p<KeyType> minKey; ///< SMA min
p<KeyType> maxKey; ///< SMA max
p<size_t> nodeLevel; ///< the height of this node
......@@ -111,7 +110,7 @@ class woPSkiplist {
* Constructor for creating a new node with an initial key and value.
*/
explicit SkipNode(const KeyType &_key, const ValueType &_value) :
bitset(1), minKey(_key), maxKey(_key), nodeLevel(0), keys(_key), values(_value) {}
bits(1), minKey(_key), maxKey(_key), nodeLevel(0), keys(_key), values(_value) {}
/**
* Check if the node is full.
......@@ -119,17 +118,17 @@ class woPSkiplist {
* @return true if the node is full
*/
inline bool isFull() const {
return bitset.get_ro().all();
return bits.get_ro().all();
}
/**
* Print this node's bounds and keys.
*/
void printNode() const {
std::cout << "\u001b[30;1m[" << minKey << "|" << maxKey << "](" << bitset.get_ro().count()
std::cout << "\u001b[30;1m[" << minKey << "|" << maxKey << "](" << bits.get_ro().count()
<< "):\u001b[0m{";
for (auto i = 0u; i < N; ++i) {
if (bitset.get_ro().test(i))
if (bits.get_ro().test(i))
std::cout << keys[i] << ",";
}
std::cout << "} --> ";
......@@ -221,7 +220,7 @@ class woPSkiplist {
*/
bool searchInNode(const SkipNode * const node, const KeyType &key, ValueType &val) const {
for (auto i = 0u; i < N; ++i) {
if (node->bitset.get_ro().test(i) && node->keys[i] == key) {
if (node->bits.get_ro().test(i) && node->keys[i] == key) {
val = node->values[i];
return true;
}
......@@ -249,8 +248,8 @@ class woPSkiplist {
}
/// Insert
auto &targetBits = targetNode->bitset.get_rw();
const auto slot = BitOperations::getFreeZero(targetBits);
auto &targetBits = targetNode->bits.get_rw();
const auto slot = targetBits.getFreeZero();
targetNode->keys[slot] = key;
targetNode->values[slot] = value;
targetBits.set(slot);
......@@ -270,10 +269,10 @@ class woPSkiplist {
pptr<SkipNode> sibling;
newSkipNode(sibling);
auto sib = sibling.get();
auto &sibBits = sib->bitset.get_rw();
auto &sibBits = sib->bits.get_rw();
auto &sibMin = sib->minKey.get_rw();
auto &sibMax = sib->maxKey.get_rw();
auto &nodeBits = node->bitset.get_rw();
auto &nodeBits = node->bits.get_rw();
/// Balance this with the new sibling node
auto sibPos = 0u;
......@@ -382,7 +381,7 @@ class woPSkiplist {
/// handle duplicates
for (auto i = 0u; i < N; ++i) {
if (node->bitset.get_ro().test(i) && node->keys[i] == key) {
if (node->bits.get_ro().test(i) && node->keys[i] == key) {
node->values[i] = value;
return false;
}
......
/*
* Copyright (C) 2017-2020 DBIS Group - TU Ilmenau, All Rights Reserved.
*
* This file is part of our NVM-based Data Structures repository.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DBIS_BITMAP_HPP
#define DBIS_BITMAP_HPP
#include <locale> ///< ctype
namespace dbis {
#define BITMAP_WORD size_t
static constexpr auto deBruijnSeq = 0x07EDD5E59A4E28C2;
static constexpr uint8_t tab64[64] = {
63, 0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61, 51, 37, 40, 49, 18,
28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19,
29, 10, 13, 21, 56, 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5};
/**
* Helper class to zero out the unused high-order bits in the highest word.
*
* @tparam EB extra bits to clean.
*/
template <size_t EB>
struct Sanitize {
static void sanitize(BITMAP_WORD& val) noexcept {
val &= ~((~static_cast<BITMAP_WORD>(0)) << EB);
}
};
template <>
struct Sanitize<0> {
static void sanitize(BITMAP_WORD) noexcept {}
};
/**
* Own bitmap implementation to get access to underlying words.
* (inspired by GCC bitset)
*
* @tparam NUM_BITS the number of bits to store.
*/
template <size_t NUM_BITS>
class Bitmap {
using Word = BITMAP_WORD;
static constexpr auto BITS_PER_WORD = sizeof(Word) * 8;
static constexpr auto NUM_WORDS = (NUM_BITS + BITS_PER_WORD - 1) / BITS_PER_WORD;
using WordArray = Word[NUM_WORDS];
WordArray words;
Word& getWord(size_t bitPos) noexcept { return words[bitPos / BITS_PER_WORD]; }
constexpr Word getWord(size_t bitPos) const noexcept { return words[bitPos / BITS_PER_WORD]; }
constexpr Word maskBit(size_t bitPos) const noexcept {
return (static_cast<Word>(1)) << (bitPos % BITS_PER_WORD);
}
private:
void check(size_t bitpos) const {
if (bitpos >= NUM_BITS)
throw std::out_of_range("pos (which is " + std::to_string(bitpos) +
") >= NUM_BITS (which is " + std::to_string(NUM_BITS) + ')');
}
void sanitize() noexcept {
Sanitize<NUM_BITS % BITS_PER_WORD>::sanitize(words[NUM_WORDS - 1]);
}
public:
/**
* "This encapsulates the concept of a single bit. An instance of this class is a proxy for
* an actual bit."
*/
class Bitref {
friend class Bitmap;
Word* wordPtr;
size_t bitPos;
Bitref();
public:
Bitref(Bitmap& bm, size_t pos) noexcept {
wordPtr = &bm.getword(pos);
bitPos = pos % BITS_PER_WORD;
}
~Bitref() noexcept {}
Bitref& operator=(bool x) noexcept {
if (x)
*wordPtr |= maskBit(bitPos);
else
*wordPtr &= ~maskBit(bitPos);
return *this;
}
Bitref& operator=(const Bitref& other) noexcept {
if ((*(other.wordPtr) & maskbit(other.bitPos)))
*wordPtr |= maskbit(bitPos);
else
*wordPtr &= ~maskbit(bitPos);
return *this;
}
bool operator~() const noexcept { return (*(wordPtr)&maskbit(bitPos)) == 0; }
operator bool() const noexcept { return (*(wordPtr)&maskbit(bitPos)) != 0; }
Bitref& flip() noexcept {
*wordPtr ^= maskbit(bitPos);
return *this;
}
}; /// end class Bitref
friend class Bitref;
/**
* Constructor for creating new array of words.
*/
constexpr Bitmap() noexcept : words() {}
/**
* Test if a bit at the given position is set.
*
* @param pos the bit position (index) to test.
* @return the true if set, false otherwise.
*/
constexpr bool test(size_t pos) const {
check(pos);
return getWord(pos) & maskBit(pos);
}
/**
* Array-indexing support.
*
* @param pos the bit position (index) to test.
* @return A bool for a const Bitmap and an instance of the proxy class Bitref for non-const
* Bitmap.
*/
constexpr bool operator[](size_t pos) const {
check(pos);
return getWord(pos) & maskBit(pos);
}
Bitref operator[](size_t pos) { return Bitref(*this, pos); }
template <class CharType, class Traits, class Alloc>
void copyToString(std::basic_string<CharType, Traits, Alloc>&, CharType, CharType) const;
template <class CharType, class Traits, class Alloc>
void copyToString(std::basic_string<CharType, Traits, Alloc>& s) const {
copyToString(s, CharType('0'), CharType('1'));
}
/**
* Determine the number of set bits.
*
* @return the counted true bits.
*/
size_t count() const noexcept {
size_t c = 0;
for (size_t i = 0; i < NUM_WORDS; ++i) {
/// http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
auto w = words[i];
w = w - ((w >> 1) & (Word) ~(Word)0 / 3);
w = (w & (Word) ~(Word)0 / 15 * 3) + ((w >> 2) & (Word) ~(Word)0 / 15 * 3);
w = (w + (w >> 4)) & (Word) ~(Word)0 / 255 * 15;
c += (Word)(w * ((Word) ~(Word)0 / 255)) >> (sizeof(Word) - 1) * 8;
}
return c;
}
/**
* Tests whether all the bits are set.
*
* @return true if all are set.
*/
bool all() const noexcept {
for (size_t i = 0; i < NUM_WORDS - 1; ++i)
if (words[i] != ~static_cast<Word>(0)) return false;
return words[NUM_WORDS - 1] ==
(~static_cast<Word>(0) >> (NUM_WORDS * BITS_PER_WORD - NUM_BITS));
}
/**
* Set all bits to its opposite value.
*
* @return a reference to the updated bitmap.
*/
Bitmap<NUM_BITS>& flip() noexcept {
for (size_t i = 0; i < NUM_WORDS; ++i) words[i] = ~words[i];
sanitize();
return *this;
}
/**
* Set all bits to true/1.
*
* @return a reference to the updated bitmap.
*/
Bitmap<NUM_BITS>& set() noexcept {
for (size_t i = 0; i < NUM_WORDS; ++i) words[i] = ~static_cast<Word>(0);
sanitize();
return *this;
}
/**
* Set bit at given position to value (default true/1).
*
* @param pos the bit position (index) to update.
* @param value either true or false, defaults to true.
* @return a reference to the updated bitmap.
*/
Bitmap<NUM_BITS>& set(size_t pos, bool value = true) {
check(pos);
if (value)
getWord(pos) |= maskBit(pos);
else
getWord(pos) &= ~maskBit(pos);
return *this;
}
/**
* Reset bit at given position.
*
* @param pos the bit position (index) to reset.
* @return a reference to the updated bitmap
*/
Bitmap<NUM_BITS>& reset(size_t pos) {
check(pos);
getWord(pos) &= ~maskBit(pos);
return *this;
}
/**
* Retrieve the underlying words of this bitmap.
*
* @return a const reference to the word array.
*/
const WordArray& getWords() const noexcept { return words; }
/**
* Find a free slot in the bitmap.
*
* @return the index of the free bit.
*/
auto getFreeZero() const noexcept {
for (size_t i = 0; i < NUM_WORDS; ++i) {
/// http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
const auto w = ~words[i]; ///< we need the complement here!
if (w != 0) {
/// count consecutive one bits using multiply and lookup
/// Applying deBruijn hash function + lookup
return 64 * i + tab64[((uint64_t)((w & -w) * deBruijnSeq)) >> 58];
}
}
/// Valid result is between 0 and 63; 64 means no free position
return 64 * NUM_WORDS;
}
/**
* Find first set position in the bitmap.
*
* @return the index of the set bit.
*/
auto getFirstSet() const noexcept {
for (size_t i = 0; i < NUM_WORDS; ++i) {
const auto w = words[i];