struct StrlenTraits { // Highway doesn't support direct `char` usage in vector types, presumably // because it can vary in signed-ness. `strlen` uses `min` to find `\0`, so // unsigned is simplest. // // It's notable that the highway quick_reference advises against unsigned // `min` operations for older CPUs: // https://google.github.io/highway/en/master/quick_reference.html#speeding-up-code-for-older-x86-platforms // // But it's unclear why; instruction timing tables indicate that unsigned and // signed bytewise `min` have identical timings. using CharType = uint8_t; using VectorTag = portable_simd::FullVector<CharType>; using VectorType = hn::VFromD<VectorTag>;
};
struct WcslenTraits { using CharType = uint32_t; using VectorTag = portable_simd::FullVector<CharType>; using VectorType = hn::VFromD<VectorTag>;
};
template <typename Traits>
PSIMD_FLATTEN static size_t strlen_vectorized(consttypename Traits::CharType* s) { using CharType = Traits::CharType; using VectorTag = Traits::VectorTag;
constexpr VectorTag d;
auto [ptr, nul_distance] = align_forward_to_vec<VectorTag>(
s, [&](auto val, optional<size_t> bytes_to_skip, optional<size_t>) -> optional<size_t> {
size_t chars_to_skip = 0; if (bytes_to_skip) { // All wide-char pointers should be suitably aligned.
PSIMD_DCHECK(*bytes_to_skip % sizeof(CharType) == 0);
chars_to_skip = *bytes_to_skip / sizeof(CharType);
} if (const optional<size_t> x = index_of_nul<Traits>(val, chars_to_skip)) { return optional{*x};
} return {};
}); if (nul_distance) { return *nul_distance;
}
// The simplest implementation from here would be: // // while (true) { // // check for nul, return if found // ++ptr; // } // // `perf` says that x86_64 CPUs stall on the 'check for nul, return if found' // branch really badly, so it's a better balance if we can work in batches. // Since batch size must be a power of two, work in batches of 4. constauto check_ptr_and_inc = [&]() -> optional<size_t> { if (const optional<const CharType*> x = ptr_of_nul<Traits>(ptr, Load(d, ptr))) { return optional{static_cast<size_t>(*x - s)};
}
ptr += d.MaxLanes(); return {};
};
// Of course, we can only work in batches if it's safe to read more than one // vector at a time. if (!kReadAheadToPageBoundaryIsOK) { while (true) { if (const optional<size_t> x = check_ptr_and_inc()) { return *x;
}
}
}
// Here, we have a trade-off to make: "how many very simple checks do we want // to do before hitting the loop that's really fast for long strings?" // // Between here and the "big string" loop, there are between 0 and 3 // `check_ptr_and_inc`s to bring us up to alignment. // // We've checked between 1 and kVectorSize bytes so far, and it seems bad to // dive into the 'big' case having checked as little as 1 byte, so add a // few checks beforehand.
constexpr size_t kMinBytesUntilStringIsBig = 128; // Worst case, we've only read 1 byte so far. Add 1 check to round up.
constexpr size_t kExtraChecksNeeded = 1 + (kMinBytesUntilStringIsBig - 1) / d.MaxBytes();
#pragma unroll for (size_t i = 0; i < kExtraChecksNeeded; ++i) { if (const optional<size_t> x = check_ptr_and_inc()) { return *x;
}
}
// Now bring ourselves to 4*kVectorAlign alignment.
constexpr size_t kFourVecAlign = 4 * vector_align(d);
static_assert(kPageSize % kFourVecAlign == 0); const size_t vector_width_from_prev_align =
(reinterpret_cast<uintptr_t>(ptr) & (kFourVecAlign - 1)) / vector_align(d); switch (vector_width_from_prev_align) { case1: if (const optional<size_t> x = check_ptr_and_inc()) { return *x;
}
[[fallthrough]]; case2: if (const optional<size_t> x = check_ptr_and_inc()) { return *x;
}
[[fallthrough]]; case3: if (const optional<size_t> x = check_ptr_and_inc()) { return *x;
} break; default:
PSIMD_DCHECK(vector_width_from_prev_align == 0);
}
const size_t index_of_first_zero = *maybe_index; // We're in the 'longer string' case, so there's no reason to assume `vec1` // is most likely to have the `\0`. Assuming all cases are equally likely, // prefer a constant 2 `ptr_of_nul` cost, rather than 1-3. if (const optional<const CharType*> x = ptr_of_nul<Traits>(ptr, min12)) { if (const optional<const CharType*> x2 = ptr_of_nul<Traits>(ptr, vec1)) { returnstatic_cast<size_t>(*x2 - s);
} // If vec1 had no zeroes, then we can assume the min 0 is from vec2. returnstatic_cast<size_t>(*x - s) + d.MaxLanes();
}
ptr += d.MaxLanes() * 2; if (const optional<const CharType*> x = ptr_of_nul<Traits>(ptr, vec3)) { returnstatic_cast<size_t>(*x - s);
}
// Since nothing else has nul in it, it's guaranteed that the nul is // vec4's, so we can use the above `index_of_nul` result.
ptr += d.MaxLanes(); returnstatic_cast<size_t>(reinterpret_cast<const CharType*>(ptr) - s) + index_of_first_zero;
}
}
} // namespace
} // namespace portable_simd
static size_t simplistic_misaligned_wcslen(constwchar_t* s) {
size_t len = 0; while (*s) {
++s;
++len;
} return len;
}
PSIMD_LIBC_FUNCTION(size_t, wcslen, constwchar_t* s) { using portable_simd::WcslenTraits;
// We might have received a misaligned pointer. Support that with a slow case // if needed. It's expected that the 99% case will be properly-aligned, so no // meaningful effort is put into making the misaligned case fast. if (reinterpret_cast<uintptr_t>(s) % alignof(wchar_t)) [[unlikely]] { return simplistic_misaligned_wcslen(s);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.