From 4f72f6b8866cfa353a29248a4f1ca7c242949231 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 22 Mar 2020 10:12:55 +0100 Subject: [PATCH] AK: Add FlyString, a simple flyweight string class FlyString is a flyweight string class that wraps a RefPtr known to be unique among the set of FlyStrings. The class is very unoptimized at the moment. When to use FlyString: - When you want O(1) string comparison - When you want to deduplicate a lot of identical strings When not to use FlyString: - For strings that don't need either of the above features - For strings that are likely to be unique --- AK/FlyString.cpp | 91 +++++++++++++++++++ AK/FlyString.h | 64 +++++++++++++ AK/Forward.h | 2 + AK/StringImpl.cpp | 3 + AK/StringImpl.h | 6 ++ AK/StringView.cpp | 8 ++ AK/StringView.h | 1 + AK/Tests/Makefile | 1 + AK/Tests/TestString.cpp | 22 +++++ DevTools/FormCompiler/Makefile | 13 +-- DevTools/IPCCompiler/Makefile | 13 +-- Kernel/Makefile | 3 +- Libraries/LibC/Makefile | 15 +-- .../Generate_CSS_PropertyID_cpp/Makefile | 13 +-- .../Generate_CSS_PropertyID_h/Makefile | 13 +-- 15 files changed, 236 insertions(+), 32 deletions(-) create mode 100644 AK/FlyString.cpp create mode 100644 AK/FlyString.h diff --git a/AK/FlyString.cpp b/AK/FlyString.cpp new file mode 100644 index 00000000000..772f100710b --- /dev/null +++ b/AK/FlyString.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +namespace AK { + +struct FlyStringImplTraits : public AK::Traits { + static unsigned hash(const StringImpl* s) { return s ? s->hash() : 0; } + static bool equals(const StringImpl* a, const StringImpl* b) + { + ASSERT(a); + ASSERT(b); + if (a == b) + return true; + if (a->length() != b->length()) + return false; + return !__builtin_memcmp(a->characters(), b->characters(), a->length()); + } +}; + +static HashTable& fly_impls() +{ + static HashTable* table; + if (!table) + table = new HashTable; + return *table; +} + +void FlyString::did_destroy_impl(Badge, StringImpl& impl) +{ + fly_impls().remove(&impl); +} + +FlyString::FlyString(const String& string) +{ + if (string.is_null()) + return; + auto it = fly_impls().find(const_cast(string.impl())); + if (it == fly_impls().end()) { + fly_impls().set(const_cast(string.impl())); + string.impl()->set_fly({}, true); + m_impl = string.impl(); + } else { + ASSERT((*it)->is_fly()); + m_impl = *it; + } +} + +FlyString::FlyString(const StringView& string) + : FlyString(static_cast(string)) +{ +} + +FlyString::FlyString(const char* string) + : FlyString(static_cast(string)) +{ +} + +int FlyString::to_int(bool& ok) const +{ + return StringUtils::convert_to_int(view(), ok); +} + +} diff --git a/AK/FlyString.h b/AK/FlyString.h new file mode 100644 index 00000000000..c9290a83132 --- /dev/null +++ b/AK/FlyString.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace AK { + +class FlyString { +public: + FlyString() {} + FlyString(const String&); + FlyString(const StringView&); + FlyString(const char*); + + bool operator==(const FlyString& other) const { return m_impl == other.m_impl; } + bool operator!=(const FlyString& other) const { return m_impl != other.m_impl; } + + const StringImpl* impl() const { return m_impl; } + const char* characters() const { return m_impl ? m_impl->characters() : nullptr; } + size_t length() const { return m_impl ? m_impl->length() : 0; } + + StringView view() const { return { characters(), length() }; } + + int to_int(bool& ok) const; + + static void did_destroy_impl(Badge, StringImpl&); + +private: + RefPtr m_impl; +}; + +template<> +struct Traits : public GenericTraits { + static unsigned hash(const FlyString& s) { return s.impl() ? s.impl()->hash() : 0; } +}; + +} + +using AK::FlyString; diff --git a/AK/Forward.h b/AK/Forward.h index 5e26c61ca82..f1336e781d9 100644 --- a/AK/Forward.h +++ b/AK/Forward.h @@ -45,6 +45,7 @@ class StringBuilder; class StringImpl; class StringView; class URL; +class FlyString; class Utf8View; template @@ -137,5 +138,6 @@ using AK::StringImpl; using AK::StringView; using AK::Traits; using AK::URL; +using AK::FlyString; using AK::Utf8View; using AK::Vector; diff --git a/AK/StringImpl.cpp b/AK/StringImpl.cpp index b47edf0be25..88b5b8f24e8 100644 --- a/AK/StringImpl.cpp +++ b/AK/StringImpl.cpp @@ -24,6 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -72,6 +73,8 @@ StringImpl::StringImpl(ConstructWithInlineBufferTag, size_t length) StringImpl::~StringImpl() { + if (m_fly) + FlyString::did_destroy_impl({}, *this); #ifdef DEBUG_STRINGIMPL --g_stringimpl_count; g_all_live_stringimpls->remove(this); diff --git a/AK/StringImpl.h b/AK/StringImpl.h index 1698d639847..87ae042b5cf 100644 --- a/AK/StringImpl.h +++ b/AK/StringImpl.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -70,11 +71,15 @@ public: return m_hash; } + bool is_fly() const { return m_fly; } + void set_fly(Badge, bool fly) const { m_fly = fly; } + private: enum ConstructTheEmptyStringImplTag { ConstructTheEmptyStringImpl }; explicit StringImpl(ConstructTheEmptyStringImplTag) + : m_fly(true) { m_inline_buffer[0] = '\0'; } @@ -89,6 +94,7 @@ private: size_t m_length { 0 }; mutable unsigned m_hash { 0 }; mutable bool m_has_hash { false }; + mutable bool m_fly { false }; char m_inline_buffer[0]; }; diff --git a/AK/StringView.cpp b/AK/StringView.cpp index ebd179299c6..e979e20fdaa 100644 --- a/AK/StringView.cpp +++ b/AK/StringView.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace AK { @@ -39,6 +40,13 @@ StringView::StringView(const String& string) { } +StringView::StringView(const FlyString& string) + : m_impl(string.impl()) + , m_characters(string.characters()) + , m_length(string.length()) +{ +} + StringView::StringView(const ByteBuffer& buffer) : m_characters((const char*)buffer.data()) , m_length(buffer.size()) diff --git a/AK/StringView.h b/AK/StringView.h index a5c1bab2b01..00e1af1e69c 100644 --- a/AK/StringView.h +++ b/AK/StringView.h @@ -55,6 +55,7 @@ public: StringView(const ByteBuffer&); StringView(const String&); + StringView(const FlyString&); bool is_null() const { return !m_characters; } bool is_empty() const { return m_length == 0; } diff --git a/AK/Tests/Makefile b/AK/Tests/Makefile index 80886dace46..ec38a5ba610 100644 --- a/AK/Tests/Makefile +++ b/AK/Tests/Makefile @@ -7,6 +7,7 @@ SHARED_TEST_SOURCES = \ ../LogStream.cpp \ ../JsonValue.cpp \ ../JsonParser.cpp \ + ../FlyString.cpp \ ../FileSystemPath.cpp \ ../URL.cpp \ ../Utf8View.cpp diff --git a/AK/Tests/TestString.cpp b/AK/Tests/TestString.cpp index 0ba1d4233dc..7377db0acc5 100644 --- a/AK/Tests/TestString.cpp +++ b/AK/Tests/TestString.cpp @@ -26,7 +26,9 @@ #include +#include #include +#include TEST_CASE(construct_empty) { @@ -137,4 +139,24 @@ TEST_CASE(to_uppercase) EXPECT(String("AbC").to_uppercase() == "ABC"); } +TEST_CASE(flystring) +{ + { + FlyString a("foo"); + FlyString b("foo"); + EXPECT_EQ(a.impl(), b.impl()); + } + + { + String a = "foo"; + FlyString b = a; + StringBuilder builder; + builder.append('f'); + builder.append("oo"); + FlyString c = builder.to_string(); + EXPECT_EQ(a.impl(), b.impl()); + EXPECT_EQ(a.impl(), c.impl()); + } +} + TEST_MAIN(String) diff --git a/DevTools/FormCompiler/Makefile b/DevTools/FormCompiler/Makefile index 317ad961192..ed927d3371d 100644 --- a/DevTools/FormCompiler/Makefile +++ b/DevTools/FormCompiler/Makefile @@ -4,14 +4,15 @@ PROGRAM = FormCompiler OBJS = \ main.o \ - ../../AK/String.o \ - ../../AK/StringImpl.o \ - ../../AK/StringBuilder.o \ - ../../AK/StringView.o \ - ../../AK/StringUtils.o \ - ../../AK/JsonValue.o \ + ../../AK/FlyString.o \ ../../AK/JsonParser.o \ + ../../AK/JsonValue.o \ ../../AK/LogStream.o \ + ../../AK/String.o \ + ../../AK/StringBuilder.o \ + ../../AK/StringImpl.o \ + ../../AK/StringUtils.o \ + ../../AK/StringView.o \ ../../Libraries/LibCore/IODevice.o \ ../../Libraries/LibCore/File.o \ ../../Libraries/LibCore/Object.o \ diff --git a/DevTools/IPCCompiler/Makefile b/DevTools/IPCCompiler/Makefile index 416c93b0fd8..498f71883f4 100644 --- a/DevTools/IPCCompiler/Makefile +++ b/DevTools/IPCCompiler/Makefile @@ -4,14 +4,15 @@ PROGRAM = IPCCompiler OBJS = \ main.o \ - ../../AK/String.o \ - ../../AK/StringImpl.o \ - ../../AK/StringBuilder.o \ - ../../AK/StringView.o \ - ../../AK/StringUtils.o \ - ../../AK/JsonValue.o \ + ../../AK/FlyString.o \ ../../AK/JsonParser.o \ + ../../AK/JsonValue.o \ ../../AK/LogStream.o \ + ../../AK/String.o \ + ../../AK/StringBuilder.o \ + ../../AK/StringImpl.o \ + ../../AK/StringUtils.o \ + ../../AK/StringView.o \ ../../Libraries/LibCore/IODevice.o \ ../../Libraries/LibCore/File.o \ ../../Libraries/LibCore/Object.o \ diff --git a/Kernel/Makefile b/Kernel/Makefile index 22e10de020f..5242bd25a62 100644 --- a/Kernel/Makefile +++ b/Kernel/Makefile @@ -1,13 +1,14 @@ OBJS = \ ../AK/FileSystemPath.o \ + ../AK/FlyString.o \ ../AK/JsonParser.o \ ../AK/JsonValue.o \ ../AK/LogStream.o \ ../AK/String.o \ ../AK/StringBuilder.o \ ../AK/StringImpl.o \ - ../AK/StringView.o \ ../AK/StringUtils.o \ + ../AK/StringView.o \ ../Libraries/LibELF/ELFImage.o \ ../Libraries/LibELF/ELFLoader.o \ ../Libraries/LibBareMetal/Output/Console.o \ diff --git a/Libraries/LibC/Makefile b/Libraries/LibC/Makefile index 3f9f00cef41..ece0689caff 100644 --- a/Libraries/LibC/Makefile +++ b/Libraries/LibC/Makefile @@ -1,16 +1,17 @@ AK_OBJS = \ - ../../AK/StringImpl.o \ - ../../AK/String.o \ - ../../AK/StringView.o \ - ../../AK/StringBuilder.o \ - ../../AK/StringUtils.o \ ../../AK/FileSystemPath.o \ - ../../AK/URL.o \ - ../../AK/JsonValue.o \ + ../../AK/FlyString.o \ ../../AK/JsonParser.o \ + ../../AK/JsonValue.o \ ../../AK/LogStream.o \ ../../AK/MappedFile.o \ ../../AK/SharedBuffer.o \ + ../../AK/String.o \ + ../../AK/StringBuilder.o \ + ../../AK/StringImpl.o \ + ../../AK/StringUtils.o \ + ../../AK/StringView.o \ + ../../AK/URL.o \ ../../AK/Utf8View.o LIBC_OBJS = \ diff --git a/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp/Makefile b/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp/Makefile index 121b113dfb2..de2039f8dae 100644 --- a/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp/Makefile +++ b/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_cpp/Makefile @@ -4,14 +4,15 @@ PROGRAM = Generate_CSS_PropertyID_cpp OBJS = \ Generate_CSS_PropertyID_cpp.o \ - ../../../../AK/String.o \ - ../../../../AK/StringImpl.o \ - ../../../../AK/StringBuilder.o \ - ../../../../AK/StringView.o \ - ../../../../AK/StringUtils.o \ - ../../../../AK/JsonValue.o \ + ../../../../AK/FlyString.o \ ../../../../AK/JsonParser.o \ + ../../../../AK/JsonValue.o \ ../../../../AK/LogStream.o \ + ../../../../AK/String.o \ + ../../../../AK/StringBuilder.o \ + ../../../../AK/StringImpl.o \ + ../../../../AK/StringUtils.o \ + ../../../../AK/StringView.o \ ../../../../Libraries/LibCore/IODevice.o \ ../../../../Libraries/LibCore/File.o \ ../../../../Libraries/LibCore/Object.o \ diff --git a/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h/Makefile b/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h/Makefile index b94e2119296..b51266128fe 100644 --- a/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h/Makefile +++ b/Libraries/LibWeb/CodeGenerators/Generate_CSS_PropertyID_h/Makefile @@ -4,14 +4,15 @@ PROGRAM = Generate_CSS_PropertyID_h OBJS = \ Generate_CSS_PropertyID_h.o \ - ../../../../AK/StringUtils.o \ - ../../../../AK/String.o \ - ../../../../AK/StringImpl.o \ - ../../../../AK/StringBuilder.o \ - ../../../../AK/StringView.o \ - ../../../../AK/JsonValue.o \ + ../../../../AK/FlyString.o \ ../../../../AK/JsonParser.o \ + ../../../../AK/JsonValue.o \ ../../../../AK/LogStream.o \ + ../../../../AK/String.o \ + ../../../../AK/StringBuilder.o \ + ../../../../AK/StringImpl.o \ + ../../../../AK/StringUtils.o \ + ../../../../AK/StringView.o \ ../../../../Libraries/LibCore/IODevice.o \ ../../../../Libraries/LibCore/File.o \ ../../../../Libraries/LibCore/Object.o \