From 45007011f89df406f46beddb296a306561a2d2ea Mon Sep 17 00:00:00 2001 From: b <1131188047@qq.com> Date: Tue, 6 Dec 2022 08:33:10 +0800 Subject: [PATCH 1/6] fix: mistake in case3 contition --- sql/README.en.md | 36 ++++++++++++++++++++++++++++++++++++ sql/README.md | 39 +++++++++++++++++++++++++++++++++++++++ sql/hash_join_iterator.cc | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 sql/README.en.md create mode 100644 sql/README.md diff --git a/sql/README.en.md b/sql/README.en.md new file mode 100644 index 000000000..3d6f714a3 --- /dev/null +++ b/sql/README.en.md @@ -0,0 +1,36 @@ +# HashJoinBloomFilter + +#### Description +{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} + +#### Software Architecture +Software architecture description + +#### Installation + +1. xxxx +2. xxxx +3. xxxx + +#### Instructions + +1. xxxx +2. xxxx +3. xxxx + +#### Contribution + +1. Fork the repository +2. Create Feat_xxx branch +3. Commit your code +4. Create Pull Request + + +#### Gitee Feature + +1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md +2. Gitee blog [blog.gitee.com](https://blog.gitee.com) +3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) +4. The most valuable open source project [GVP](https://gitee.com/gvp) +5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) +6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/sql/README.md b/sql/README.md new file mode 100644 index 000000000..7f9598baf --- /dev/null +++ b/sql/README.md @@ -0,0 +1,39 @@ +# HashJoinBloomFilter + +#### 介绍 +{**以下是 Gitee 平台说明,您可以替换此简介** +Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 +无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} + +#### 软件架构 +软件架构说明 + + +#### 安装教程 + +1. xxxx +2. xxxx +3. xxxx + +#### 使用说明 + +1. xxxx +2. xxxx +3. xxxx + +#### 参与贡献 + +1. Fork 本仓库 +2. 新建 Feat_xxx 分支 +3. 提交代码 +4. 新建 Pull Request + + +#### 特技 + +1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md +2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) +3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 +4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 +5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) +6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/sql/hash_join_iterator.cc b/sql/hash_join_iterator.cc index e22d62971..9cc79bde7 100644 --- a/sql/hash_join_iterator.cc +++ b/sql/hash_join_iterator.cc @@ -1511,7 +1511,7 @@ static bool ShouldStartRecursiveJoin( // I/O cost of probe row saving file if (!is_inner_join) { cost_direct += ceil(alpha) * num_row_probe_chunk; - cost_recursive += (ceil((alpha - 1) / max_bucket_count) + 1) * num_row_probe_chunk; + cost_recursive += ceil((alpha - 1) / max_bucket_count) * num_row_probe_chunk; } return cost_direct > cost_recursive * 1.05; } \ No newline at end of file -- Gitee From f94228443fefe6eec234b67a89346f344a89d023 Mon Sep 17 00:00:00 2001 From: b <1131188047@qq.com> Date: Tue, 6 Dec 2022 17:54:55 +0800 Subject: [PATCH 2/6] fix: bloom condition, case3 condition --- sql/hash_join_iterator.cc | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/sql/hash_join_iterator.cc b/sql/hash_join_iterator.cc index 9cc79bde7..94bd161b1 100644 --- a/sql/hash_join_iterator.cc +++ b/sql/hash_join_iterator.cc @@ -64,7 +64,7 @@ static bool ShouldStartRecursiveJoin( static bool NeedBloom(THD *thd, const unique_ptr_destroy_only &build_input, const unique_ptr_destroy_only &probe_input, const Prealloced_array &m_join_conditions, - size_t max_bloom_size, bool is_recursive_join); + size_t max_bloom_size, bool is_recursive_join, bool allow_spill_to_disk); HashJoinIterator::HashJoinIterator( THD *thd, unique_ptr_destroy_only build_input, @@ -263,10 +263,11 @@ bool HashJoinIterator::Init() { m_current_chunk = -1; // The HashJoinIterator::Init() will be called multiple times when there are - // subqueries, so the need_bloom state will have to be reset each time the - // Init() is called. + // subqueries, so the need_bloom state will have to be reset each time Init() + // is called. bool need_bloom = NeedBloom(thd(), m_build_input, m_probe_input, m_join_conditions, - m_row_buffer.bloom_filter_get_max_mem(), m_recursion_depth > 0); + m_row_buffer.bloom_filter_get_max_mem(), + m_recursion_depth > 0, m_allow_spill_to_disk); m_row_buffer.set_need_bloom(need_bloom); // Build the hash table. @@ -540,6 +541,9 @@ bool HashJoinIterator::BuildHashTable() { // that we only read unmatched probe rows. InitWritingToProbeRowSavingFile(); } + if(ConstructBloomWithHashStore()){ + return true; + } SetReadingProbeRowState(); return false; } @@ -1409,8 +1413,9 @@ HashJoinIterator *HashJoinIterator::GetRootHJIterator() { static bool NeedBloom(THD *thd, const unique_ptr_destroy_only &build_input, const unique_ptr_destroy_only &probe_input, const Prealloced_array &m_join_conditions, - size_t max_bloom_size, bool is_recursive_join) { + size_t max_bloom_size, bool is_recursive_join, bool allow_spill_to_disk) { if (max_bloom_size == 0) return false; + if (allow_spill_to_disk == 0) return false; bool build_is_table_scan_iter = typeid(*build_input.get()) == typeid(TableScanIterator); bool probe_is_table_scan_iter = typeid(*probe_input.get()) == typeid(TableScanIterator); @@ -1507,11 +1512,11 @@ static bool ShouldStartRecursiveJoin( double cost_direct = num_row_build_chunk + ceil(alpha) * num_row_probe_chunk; double cost_recursive = 3 * num_row_build_chunk + (2 + ceil((alpha - 1) / max_bucket_count)) * num_row_probe_chunk - num_read; - - // I/O cost of probe row saving file - if (!is_inner_join) { - cost_direct += ceil(alpha) * num_row_probe_chunk; - cost_recursive += ceil((alpha - 1) / max_bucket_count) * num_row_probe_chunk; + + // bad-case estimation for the probe row savings. + if(!is_inner_join) { + cost_recursive += (ceil((alpha - 1) / max_bucket_count) - 1) * num_row_probe_chunk / 2; } + return cost_direct > cost_recursive * 1.05; } \ No newline at end of file -- Gitee From f20d48a4d7cfbf8c7fabdfb0edc8c337aa5622fa Mon Sep 17 00:00:00 2001 From: b <1131188047@qq.com> Date: Tue, 6 Dec 2022 18:44:46 +0800 Subject: [PATCH 3/6] upd: case3 condition --- sql/hash_join_iterator.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/hash_join_iterator.cc b/sql/hash_join_iterator.cc index 94bd161b1..cb9090cbf 100644 --- a/sql/hash_join_iterator.cc +++ b/sql/hash_join_iterator.cc @@ -1513,9 +1513,8 @@ static bool ShouldStartRecursiveJoin( double cost_recursive = 3 * num_row_build_chunk + (2 + ceil((alpha - 1) / max_bucket_count)) * num_row_probe_chunk - num_read; - // bad-case estimation for the probe row savings. if(!is_inner_join) { - cost_recursive += (ceil((alpha - 1) / max_bucket_count) - 1) * num_row_probe_chunk / 2; + // The additional cost caused by probe row saving should be the same. } return cost_direct > cost_recursive * 1.05; -- Gitee From 81b7311a658de0943a3f95d4e8ddf0fc6e347e99 Mon Sep 17 00:00:00 2001 From: b <1131188047@qq.com> Date: Thu, 8 Dec 2022 09:16:34 +0800 Subject: [PATCH 4/6] add: patch of hashjoin with log & atomicbloom --- patches/atomicBloom.patch | 107 ++++++++ patches/bloomLog.patch | 518 ++++++++++++++++++++++++++++++++++++++ patches/mysqld_log.docx | Bin 0 -> 22903 bytes patches/readme.txt | 5 + 4 files changed, 630 insertions(+) create mode 100644 patches/atomicBloom.patch create mode 100644 patches/bloomLog.patch create mode 100644 patches/mysqld_log.docx create mode 100644 patches/readme.txt diff --git a/patches/atomicBloom.patch b/patches/atomicBloom.patch new file mode 100644 index 000000000..8250bc328 --- /dev/null +++ b/patches/atomicBloom.patch @@ -0,0 +1,107 @@ +diff --git a/sql/bloom_helpers.h b/sql/bloom_helpers.h +index 0091a48..2b8a990 100644 +--- a/sql/bloom_helpers.h ++++ b/sql/bloom_helpers.h +@@ -6,6 +6,7 @@ + #include + #include + #include ++#include + + #include "m_ctype.h" + #include "my_inttypes.h" +@@ -86,6 +87,81 @@ private: + segment_type *segment_arr = nullptr; + }; + ++// atomic version ++template ++class bloom_filter> { ++ private: ++ using segment_type = std::atomic; ++ const size_t segment_length_in_bytes = sizeof(segment_internal_type); // 1 2 4 8 ++ const size_t segment_length_in_bits = segment_length_in_bytes * 8; // 8 16 32 64 ++ const size_t segment_len_nbits = log2(segment_length_in_bits); // 3 4 5 6 ++ const size_t segment_mask = (1 << segment_len_nbits) - 1; // 7 15 31 63 ++ ++ inline size_t get_bitseg(size_t h_val, size_t index, size_t mask) { ++ return (h_val >> (index * segment_len_nbits)) & mask; ++ } ++ ++ public: ++ bloom_filter() { } ++ ++ /// Initialize a BloomFilter. ++ /// ++ /// @param bloom_size ++ /// size of the bloom filter, in bytes. ++ /// @param mem_root ++ /// the MEM_ROOT to allocate memory on for the bloom filter. ++ bool Init(size_t bloom_size, MEM_ROOT *mem_root) { ++ if(bloom_size < sizeof(segment_type)) { ++ bloom_size = sizeof(segment_type); ++ } ++ segment_arr = (segment_type*)mem_root->Alloc(bloom_size); ++ if (segment_arr == nullptr) { ++ return true; ++ } ++ ++ n_segments = bloom_size / segment_length_in_bytes; ++ segment_index_mask = n_segments - 1; ++ ++ for (int i = 0; i < n_segments; i++) { ++ segment_arr[i] = 0; ++ } ++ ++ return false; ++ } ++ ++ void set(size_t h_val) { ++ size_t seg_offset = get_bitseg(h_val, 2, segment_index_mask); ++ size_t offset1 = get_bitseg(h_val, 1, segment_mask); ++ size_t offset2 = get_bitseg(h_val, 0, segment_mask); ++ ++ segment_internal_type to_insert = 0; ++ to_insert |= (((segment_internal_type)1) << offset1); ++ to_insert |= (((segment_internal_type)1) << offset2); ++ ++ segment_arr[seg_offset].fetch_or(to_insert); ++ } ++ bool test(size_t h_val) { ++ size_t seg_offset = get_bitseg(h_val, 2, segment_index_mask); ++ size_t offset1 = get_bitseg(h_val, 1, segment_mask); ++ size_t offset2 = get_bitseg(h_val, 0, segment_mask); ++ ++ segment_internal_type to_insert = 0; ++ to_insert |= (((segment_internal_type)1) << offset1); ++ to_insert |= (((segment_internal_type)1) << offset2); ++ ++ segment_internal_type prev = segment_arr[seg_offset]; ++ ++ return ((prev & to_insert) != to_insert); ++ } ++ ++ size_t getSize() { return n_segments * segment_length_in_bytes; } ++ ++private: ++ size_t n_segments; ++ size_t segment_index_mask; ++ segment_type *segment_arr = nullptr; ++}; ++ + template + class HyperLogLog { + const double pow_2_64 = pow(2.0, 64.0); +diff --git a/sql/hash_join_buffer.h b/sql/hash_join_buffer.h +index 3d1457c..22d98fd 100644 +--- a/sql/hash_join_buffer.h ++++ b/sql/hash_join_buffer.h +@@ -372,7 +372,7 @@ class HashJoinRowBuffer { + hash_map_iterator m_last_row_stored; + + // The following functions and fields are related to bloom filters +- using bloom_filter_type = bloom_filter; ++ using bloom_filter_type = bloom_filter>; + const size_t m_bloom_max_mem_available; + unique_ptr_destroy_only m_bloom_filter; + unique_ptr_destroy_only m_hash_store; diff --git a/patches/bloomLog.patch b/patches/bloomLog.patch new file mode 100644 index 000000000..07b8b992a --- /dev/null +++ b/patches/bloomLog.patch @@ -0,0 +1,518 @@ +diff --git a/sql/hash_join_buffer.h b/sql/hash_join_buffer.h +index 3d1457c..e5354e4 100644 +--- a/sql/hash_join_buffer.h ++++ b/sql/hash_join_buffer.h +@@ -391,6 +391,13 @@ class HashJoinRowBuffer { + bool need_bloom() const { return m_need_bloom; } + void set_need_bloom(bool need_bloom) { m_need_bloom = need_bloom; } + ++ public: ++ //test ++ uint64_t m_bloomdbg_n_not_filtered = 0; // 没被过滤 ++ uint64_t m_bloomdbg_n_filtered = 0; // 被过滤 ++ uint64_t m_bloomdbg_n_false_positive = 0; // 误判(被判有实际上没有) ++ uint64_t m_bloomdbg_n_negative = 0; // 实际上没有连接结果的 ++ uint64_t m_bloomdbg_n_write_probe = 0; // 被驱动表落盘了多少行 + }; + } // namespace hash_join_buffer + +diff --git a/sql/hash_join_iterator.cc b/sql/hash_join_iterator.cc +index cb9090c..2f582f3 100644 +--- a/sql/hash_join_iterator.cc ++++ b/sql/hash_join_iterator.cc +@@ -55,6 +55,15 @@ + #include "tables_contained_in.h" + + constexpr size_t HashJoinIterator::kMaxChunks; ++//case3修改 ++#include ++static time_t timeMs() { ++ using std::chrono::duration_cast; ++ using std::chrono::milliseconds; ++ using std::chrono::seconds; ++ using std::chrono::system_clock; ++ return duration_cast(system_clock::now().time_since_epoch()).count(); ++} + + static bool ShouldStartRecursiveJoin( + size_t max_bloom_size, double num_row_build_chunk, double num_row_probe_chunk, +@@ -64,7 +73,24 @@ static bool ShouldStartRecursiveJoin( + static bool NeedBloom(THD *thd, const unique_ptr_destroy_only &build_input, + const unique_ptr_destroy_only &probe_input, + const Prealloced_array &m_join_conditions, +- size_t max_bloom_size, bool is_recursive_join, bool allow_spill_to_disk); ++ size_t max_bloom_size, bool is_recursive_join, bool allow_spill_to_disk,int thr); ++ ++//max_bloom_size=0:不开布隆 ++//max_bloom_size>0:允许布隆,布隆占字节数不超过这个数 ++//原本max_bloom_size=0时就不允许递归,但此测试版本里以下情况例外(无论max_bloom_size是多少): ++//hash_join_test=3:不允许递归 ++//hash_join_test=4:允许递归 ++//hash_join_test=6:如果m_allow_spill_disk==true则强制开布隆 ++//hash_join_test=7:第零层判断是否进行第一层递归时,强行返回“是” ++//hash_join_test=其他:没有效果 ++ ++//综上: ++//源码流程:设置hash_join_test =3,max_bloom_size=0 ++//布隆+递归流程:设置max_bloom_size>0,hash_join_test=4 ++//只开递归:设置max_bloom_size=0,hash_join_test=4 ++//只开布隆:设置max_bloom_size>0,hash_join_test=3 ++ ++static const bool countErr = true;//统计误判率。注:会造成明显的额外开销(5%+),log输出的[HashStore] read=xxx也会增大! + + HashJoinIterator::HashJoinIterator( + THD *thd, unique_ptr_destroy_only build_input, +@@ -206,6 +232,7 @@ bool HashJoinIterator::InitProbeIterator() { + } + + bool HashJoinIterator::Init() { ++ hash_join_test = thd()->variables.hash_join_test; + // Clean up memory allocated by sub-iterators. + subIterator.reset(nullptr); + m_subiterator_mem_root.Clear(); +@@ -261,13 +288,15 @@ bool HashJoinIterator::Init() { + m_build_chunk_current_row = 0; + m_probe_chunk_current_row = 0; + m_current_chunk = -1; ++m_debug_begin_time = timeMs(); + + // The HashJoinIterator::Init() will be called multiple times when there are + // subqueries, so the need_bloom state will have to be reset each time Init() + // is called. + bool need_bloom = NeedBloom(thd(), m_build_input, m_probe_input, m_join_conditions, + m_row_buffer.bloom_filter_get_max_mem(), +- m_recursion_depth > 0, m_allow_spill_to_disk); ++ m_recursion_depth > 0, m_allow_spill_to_disk, ++ hash_join_test); + m_row_buffer.set_need_bloom(need_bloom); + + // Build the hash table. +@@ -275,6 +304,7 @@ bool HashJoinIterator::Init() { + DBUG_ASSERT(thd()->is_error()); // my_error should have been called. + return true; + } ++ m_debug_build_time = timeMs() - m_debug_begin_time;//case3修改 + + if (m_state == State::END_OF_ROWS) { + // BuildHashTable() decided that the join is done (the build input is +@@ -639,7 +669,7 @@ bool HashJoinIterator::ReadNextHashJoinChunk() { + if (move_to_next_chunk) { + m_current_chunk++; + m_build_chunk_current_row = 0; +- ++m_debug_n_memfull_logged=false; + // Since we are moving to a new set of chunk files, ensure that we read from + // the chunk file and not from the probe row saving file. + m_read_from_probe_row_saving = false; +@@ -688,13 +718,29 @@ bool HashJoinIterator::ReadNextHashJoinChunk() { + // if it is not required this time, it will not be required the next time + // we get here. Therefore, there won't be duplicate join results. + bool m_recursive_join = ShouldStartRecursiveJoin( +- m_row_buffer.bloom_filter_get_max_mem(), +- build_chunk.num_rows(), +- m_chunk_files_on_disk[m_current_chunk].probe_chunk.num_rows(), +- m_build_chunk_current_row, m_recursion_depth, +- hash_join_recursion_bucket_limit, +- hash_join_recursion_depth_limit, m_join_type == JoinType::INNER, +- m_has_dominant_hash); ++ hash_join_test == 4 ? 100000 : //hash_join_test=4时允许递归 ++ hash_join_test == 3 ? 0 : m_row_buffer.bloom_filter_get_max_mem(),//hash_join_test=3时不开递归 ++ build_chunk.num_rows(), ++ m_chunk_files_on_disk[m_current_chunk].probe_chunk.num_rows(), ++ m_build_chunk_current_row, m_recursion_depth, ++ hash_join_recursion_bucket_limit, hash_join_recursion_depth_limit, ++ m_join_type == JoinType::INNER, m_has_dominant_hash); ++ if(hash_join_test==7&&m_recursion_depth == 0)m_recursive_join=true;//hash_join_test=7时强制开一层递归 ++ ++ // 打印调试信息 ++ if (m_recursion_depth == 0 && !m_debug_n_memfull_logged) { ++ m_debug_n_memfull_logged=true; ++ ++ //printf("[full] idx=%u depth=%u B=%lld P=%lld P/b=%f B/b=%f case3=%d)\n", ++ printf("[full] idx=%u B=%lld P=%lld P/b=%f B/b=%f case3=%d\n", ++ m_current_chunk, ++ //m_recursion_depth, ++ build_chunk.num_rows(), ++ m_chunk_files_on_disk[m_current_chunk].probe_chunk.num_rows(), ++ 1.0f * m_chunk_files_on_disk[m_current_chunk].probe_chunk.num_rows() / m_build_chunk_current_row, ++ 1.0f * build_chunk.num_rows() / m_build_chunk_current_row, ++ m_recursive_join); ++ } + + if(m_recursive_join) { + if (InitRowBuffer()) { // Free memory allocated by hash map. +@@ -792,11 +838,18 @@ bool HashJoinIterator::ReadRowFromProbeIterator() { + if (m_allow_spill_to_disk) { + m_hash_join_type = HashJoinType::SPILL_TO_DISK; + m_state = State::LOADING_NEXT_CHUNK_PAIR; ++ /*if(m_recursion_depth==0){ ++ printf("[probeEnd] thr=%d need=%d filtered=%lu/%lu=%f err: %lu/%lu=%f\n", hash_join_test, ++ m_row_buffer.need_bloom(), m_row_buffer.m_bloomdbg_n_filtered, m_row_buffer.m_bloomdbg_n_not_filtered+m_row_buffer.m_bloomdbg_n_filtered, ++ 1.0f*m_row_buffer.m_bloomdbg_n_filtered/(m_row_buffer.m_bloomdbg_n_not_filtered+m_row_buffer.m_bloomdbg_n_filtered), ++ m_row_buffer.m_bloomdbg_n_false_positive,m_row_buffer.m_bloomdbg_n_negative, ++ 1.0f*m_row_buffer.m_bloomdbg_n_false_positive/(m_row_buffer.m_bloomdbg_n_negative)); ++ }*/ + + // We currently disable bloom filter while processing chunk files. + m_row_buffer.set_need_bloom(false); + m_probe_row_match_flag = false; +- ++ + return false; + } + +@@ -966,6 +1019,7 @@ void HashJoinIterator::LookupProbeRowInHashTable() { + } + return; + } ++ bool needbl=m_row_buffer.need_bloom(); + + hash_join_buffer::Key key( + pointer_cast(m_temporary_row_and_join_key_buffer.ptr()), +@@ -973,7 +1027,8 @@ void HashJoinIterator::LookupProbeRowInHashTable() { + + if (m_row_buffer.need_bloom() && + m_row_buffer.bloom_filter_test(m_row_buffer.bloom_filter_hash(key))) { +- m_filtered_by_bloom = true; ++ m_filtered_by_bloom = true; ++ m_row_buffer.m_bloomdbg_n_filtered++; + if (m_join_type == JoinType::ANTI || m_join_type == JoinType::OUTER) { + m_hash_map_iterator = m_row_buffer.end(); + m_hash_map_end = m_row_buffer.end(); +@@ -989,9 +1044,18 @@ void HashJoinIterator::LookupProbeRowInHashTable() { + } else { + SetReadingProbeRowState(); + } ++ if(countErr){//没有结果 ++ m_row_buffer.m_bloomdbg_n_negative++; ++ } + return; + } + ++ //没被过滤,说明期望是有结果的。 ++ ++ if(needbl){ ++ m_row_buffer.m_bloomdbg_n_not_filtered++; ++ } ++ + + if ((m_join_type == JoinType::SEMI || m_join_type == JoinType::ANTI) && + m_extra_condition == nullptr) { +@@ -1010,6 +1074,12 @@ void HashJoinIterator::LookupProbeRowInHashTable() { + m_hash_map_iterator = range.first; + m_hash_map_end = range.second; + } ++ if(needbl){ ++ if(countErr && m_dbg_build_hashes.find(m_row_buffer.bloom_filter_hash(key)) == m_dbg_build_hashes.end()) {//判错了 ++ m_row_buffer.m_bloomdbg_n_false_positive++; ++ m_row_buffer.m_bloomdbg_n_negative++; ++ } ++ } + + m_state = State::READING_FIRST_ROW_FROM_HASH_TABLE; + } +@@ -1050,6 +1120,7 @@ bool HashJoinIterator::WriteProbeRowToDiskIfApplicable() { + if ((m_join_type == JoinType::INNER || m_join_type == JoinType::OUTER) || + !found_match) { + if (on_disk_hash_join() && m_current_chunk == -1) { ++ m_row_buffer.m_bloomdbg_n_write_probe++; + if (WriteRowToChunk(thd(), &m_chunk_files_on_disk, + false /* write_to_build_chunk */, + m_probe_input_tables, m_join_conditions, +@@ -1254,6 +1325,9 @@ int HashJoinIterator::Read() { + return result; + } + case State::END_OF_ROWS: ++ //if(m_recursion_depth == 0) printf("HashJoin end. savingfile read=%d write=%d", n_probe_read, n_probe_write);//case3 打印一下 ++ m_debug_total_time = timeMs() - m_debug_begin_time; ++ PrintDebugInfo(); + return -1; + } + } +@@ -1311,7 +1385,7 @@ std::vector HashJoinIterator::DebugString() const { + } + + bool HashJoinIterator::InitWritingToProbeRowSavingFile() { +- m_write_to_probe_row_saving = true; ++ m_write_to_probe_row_saving = true;n_probe_write++; + return m_probe_row_saving_write_file.Init(m_probe_input_tables, + m_join_type == JoinType::OUTER); + } +@@ -1319,7 +1393,7 @@ bool HashJoinIterator::InitWritingToProbeRowSavingFile() { + bool HashJoinIterator::InitReadingFromProbeRowSavingFile() { + m_probe_row_saving_read_file = std::move(m_probe_row_saving_write_file); + m_probe_row_saving_read_file_current_row = 0; +- m_read_from_probe_row_saving = true; ++ m_read_from_probe_row_saving = true;n_probe_read++; + return m_probe_row_saving_read_file.Rewind(); + } + +@@ -1408,14 +1482,90 @@ HashJoinIterator *HashJoinIterator::GetRootHJIterator() { + if(m_recursion_depth == 0) return this; + return ((HashJoinChunkIterator*) m_build_input.get())->GetParentHashJoinIterator()->GetRootHJIterator(); + } ++std::string HashJoinIterator::GetTableString(hash_join_buffer::TableCollection m_tables) { ++ std::stringstream ss; ++ int f = 0; ++ for (const hash_join_buffer::Table &it : m_tables.tables()) { ++ if(f++) ss<<","; ++ ss<table()->alias; ++ } ++ return ss.str(); ++} ++ ++void HashJoinIterator::PrintDebugInfo() { ++ if(m_debug_mute) return; ++ if(m_debug_logged) return;//只打印一次 ++ m_debug_logged = true; ++ ++ HashJoinIterator *root = GetRootHJIterator(); ++ ++ root->m_debug_build_time_arr[m_recursion_depth] += m_debug_build_time; ++ root->m_debug_total_time_arr[m_recursion_depth] += m_debug_total_time; ++ root->m_debug_n_cnt_arr[m_recursion_depth]++; ++ root->m_debug_n_chunk_arr[m_recursion_depth] += m_chunk_files_on_disk.size(); ++ root->m_debug_pf_read_arr[m_recursion_depth] += n_probe_read; ++ root->m_debug_pf_write_arr[m_recursion_depth] += n_probe_write; ++ root->m_debug_bloom[m_recursion_depth] += m_row_buffer.need_bloom(); ++ root->m_debug_bloom_err[m_recursion_depth] += m_row_buffer.m_bloomdbg_n_false_positive; ++ root->m_debug_bloom_filtered[m_recursion_depth] += m_row_buffer.m_bloomdbg_n_filtered; ++ root->m_debug_bloom_nfil[m_recursion_depth] += m_row_buffer.m_bloomdbg_n_not_filtered; ++ root->m_debug_bloom_noresult[m_recursion_depth] += m_row_buffer.m_bloomdbg_n_negative; ++ ++ int d = m_recursion_depth; ++ int (&dbg_info)[129] = root->m_debug_n_buckets[d]; ++ dbg_info[m_chunk_files_on_disk.size()]++; ++ ++ std::stringstream ss; ++ ++ if(m_recursion_depth == 0) { ++ ss<<"[HashJoinIterator] input="< 0; i++) { ++ std::stringstream b; ++ for(int j = 0, f = 0; j < 129; j++){ ++ if(m_debug_n_buckets[i][j]){ ++ b << (f++ ? "," : "") << j << "x" << m_debug_n_buckets[i][j]; ++ } ++ } ++ int cn = m_debug_n_cnt_arr[i]; ++ //printf("Depth=%d count=%d total=%.1fs(avg:%.2fs) build=%.1fs(avg:%.2fs) avg_n_chunk=%.1f chunk_arr=%s pr=%d pw=%d filtered=%lld/%lld=%f err=%lld/%lld=%f\n", ++ printf("Depth=%d count=%d total=%.1fs(avg:%.2fs) build=%.1fs(avg:%.2fs) filtered=%lld/%lld=%f err=%lld/%lld=%f\n", ++ i, cn, ++ m_debug_total_time_arr[i]/1000.0, m_debug_total_time_arr[i]/cn/1000.0, ++ m_debug_build_time_arr[i]/1000.0, m_debug_build_time_arr[i]/cn/1000.0, ++ //m_debug_n_chunk_arr[i]/cn, ++ //b.str().c_str(), ++ //m_debug_pf_read_arr[i], m_debug_pf_write_arr[i], ++ m_debug_bloom_filtered[i], m_debug_bloom_nfil[i]+m_debug_bloom_filtered[i], ++ 1.0f*m_debug_bloom_filtered[i]/(m_debug_bloom_nfil[i]+m_debug_bloom_filtered[i]), ++ m_debug_bloom_err[i], m_debug_bloom_noresult[i], ++ 1.0f*m_debug_bloom_err[i]/m_debug_bloom_noresult[i]); ++ } ++ printf("==========\n"); ++ } ++} + + // Determine whether a bloom filter should be used. + static bool NeedBloom(THD *thd, const unique_ptr_destroy_only &build_input, + const unique_ptr_destroy_only &probe_input, + const Prealloced_array &m_join_conditions, +- size_t max_bloom_size, bool is_recursive_join, bool allow_spill_to_disk) { +- if (max_bloom_size == 0) return false; +- if (allow_spill_to_disk == 0) return false; ++ size_t max_bloom_size, bool is_recursive_join, bool allow_spill_to_disk, int thr) { ++ if (max_bloom_size == 0){ ++ if(!is_recursive_join){ ++ printf("[NeedBloom] false (maxsize=0)\n"); ++ } ++ return false; ++ } ++ if (allow_spill_to_disk == 0) { ++ if(!is_recursive_join){ ++ printf("[NeedBloom] false (allow_spill_to_disk=0)\n"); ++ } ++ return false; ++ } + + bool build_is_table_scan_iter = typeid(*build_input.get()) == typeid(TableScanIterator); + bool probe_is_table_scan_iter = typeid(*probe_input.get()) == typeid(TableScanIterator); +@@ -1467,6 +1617,14 @@ static bool NeedBloom(THD *thd, const unique_ptr_destroy_only &buil + bool same_table_and_field = field_same == 0 && table_same == 0; + bool need_bloom = !build_input_too_big && !same_table_and_field && !no_condition; + ++ if(thr == 6) need_bloom = true; ++ ++ if(!is_recursive_join){ ++ printf("[NeedBloom] nBbuildRow=%.0f nProbeRow=%.0f buildTooBig=%d noCondition=%d sameTable&Field=%d thr=%d needBloom=%d\n", ++ build_input->expected_rows(), probe_input->expected_rows(), build_input_too_big, no_condition, ++ same_table_and_field, thr, need_bloom); ++ } ++ + return need_bloom; + } + +@@ -1477,23 +1635,39 @@ bool HashJoinIterator::ConstructBloomWithHashStore() { + ha_rows n_rows = hash_store->GetNumberOfRows(); + size_t cardinality = hash_store->EstimateCardinality(); + hash_store->Rewind(); ++ if(m_recursion_depth==0&&m_state==State::READING_ROW_FROM_PROBE_ITERATOR) ++ printf("[HashStore] nRows=%lld cardinality=%ld ", n_rows, cardinality); + + size_t bloom_size = (size_t)((-1.0) * cardinality * log(0.05) / + (log(2.0) * log(2.0))); + if(m_row_buffer.bloom_filter_init(bloom_size)){ + return true; + } ++ ++ if(countErr)m_dbg_build_hashes.clear(); + +- size_t tmp; ++ long long st = timeMs(); //测试用 ++ size_t chksum = 0; //测试用 ++ size_t tmp = 0; + for (ha_rows i = 0; i < n_rows; i++) { + if (hash_store->ReadKey(tmp)) { + return true; + } ++ if(countErr) m_dbg_build_hashes.insert(tmp); ++ chksum ^= tmp; + m_row_buffer.bloom_filter_set(tmp); + } + + m_has_dominant_hash = m_row_buffer.has_dominant_hash(); + ++ if(m_recursion_depth==0&&m_state==State::READING_ROW_FROM_PROBE_ITERATOR){ ++ if(countErr) printf("actualCard=%lu cardErr=%.5f ",m_dbg_build_hashes.size(), 1.0*m_dbg_build_hashes.size()/cardinality-1.0); ++ //printf("chksum=%ld read=%lldms bloom_size=%zu actual_size=%zu has_dominant_hash=%d\n", ++ // chksum, timeMs() - st, bloom_size, m_row_buffer.bloom_filter_get_size(), m_has_dominant_hash); ++ printf(" read=%lldms bloom_size=%zu actual_size=%zu has_dominant_hash=%d\n", ++ timeMs() - st, bloom_size, m_row_buffer.bloom_filter_get_size(), m_has_dominant_hash); ++ } ++ + return false; + } + +@@ -1518,4 +1692,4 @@ static bool ShouldStartRecursiveJoin( + } + + return cost_direct > cost_recursive * 1.05; +-} +\ No newline at end of file ++} +diff --git a/sql/hash_join_iterator.h b/sql/hash_join_iterator.h +index 9072b50..6c1be4e 100644 +--- a/sql/hash_join_iterator.h ++++ b/sql/hash_join_iterator.h +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + + #include "my_alloc.h" + #include "my_inttypes.h" +@@ -663,6 +664,7 @@ class HashJoinIterator final : public RowIterator { + unique_ptr_destroy_only build_chunk_iterator; + unique_ptr_destroy_only probe_chunk_iterator; + ++ + static constexpr uint32 hash_join_recursion_depth_limit = 4; + static constexpr uint32 hash_join_recursion_bucket_limit = 16; + +@@ -670,8 +672,37 @@ class HashJoinIterator final : public RowIterator { + + bool ConstructBloomWithHashStore(); + ++//////////测试用的变量////////// ++ int n_probe_read = 0;//读了几次ProbeRowSavingFile ++ int n_probe_write = 0;//写了几次ProbeRowSavingFile ++ ++ //下面几个应当是从thd.variables里取来的变量,暂时设为定值 ++ uint32 hash_join_test; ++ bool m_debug_mute = false;//是否不输出信息 ++ bool m_debug_logged = false;//是否已经输出过调试信息,避免太啰嗦只输出一次。 ++ bool m_debug_n_memfull_logged = 0;//[full]信息输出了没有 ++ //std::string m_debug_info[10];//各层的递归信息 ++ int m_debug_n_buckets[10][129]={0}; ++ time_t m_debug_build_time_arr[10]={0}; ++ time_t m_debug_total_time_arr[10]={0}; ++ int m_debug_n_cnt_arr[10]={0};//第i层递归执行了几次 ++ double m_debug_n_chunk_arr[10]={0.0};//第i层递归分块数之和 ++ time_t m_debug_begin_time = 0;//测试用,记录哈希连接开始的时间 ++ time_t m_debug_build_time = 0; ++ time_t m_debug_total_time = 0; ++ int m_debug_pf_read_arr[10]={0}; ++ int m_debug_pf_write_arr[10]={0}; ++ int m_debug_bloom[10]={0};//废弃 ++ long long m_debug_bloom_filtered[10]={0}; ++ long long m_debug_bloom_nfil[10]={0}; ++ long long m_debug_bloom_err[10]={0}; ++ long long m_debug_bloom_noresult[10]={0}; + HashJoinIterator *GetRootHJIterator(); +- ++ void PrintDebugInfo(); ++ std::string GetTableString(hash_join_buffer::TableCollection m_tables); ++ std::set m_dbg_build_hashes; ++//////////测试结束////////// ++ + bool BeginRecursiveHashJoin(); + + // The level of the recursive partition +diff --git a/sql/sql_union.cc b/sql/sql_union.cc +index 70e8dee..5f0a9bd 100644 +--- a/sql/sql_union.cc ++++ b/sql/sql_union.cc +@@ -1232,6 +1232,21 @@ bool SELECT_LEX_UNIT::execute(THD *thd) { + */ + Change_current_select save_select(thd); + ++ printf("================================= QUERY =================================\n"); ++ const MYSQL_LEX_CSTRING &qstr = thd->query(); ++ static char buf[1024 * 32]; ++ for (size_t i = 0; i < qstr.length; i++) buf[i] = (qstr.str[i] == '\n' || qstr.str[i] == '\r') ? '|' : qstr.str[i]; ++ buf[qstr.length] = '\0'; ++ printf("%s\n", buf); ++ printf("join_buff_size = %ld\n", thd->variables.join_buff_size); ++ //printf("hash_join_recursion_depth_limit = %u\n", ++ // thd->variables.hash_join_recursion_depth_limit); ++ printf("hash_join_test = %u\n", ++ thd->variables.hash_join_test); ++ printf("hash_join_max_bloom_size = %lu\n", ++ thd->variables.hash_join_max_bloom_size); ++ printf("=========================================================================\n"); ++ + return ExecuteIteratorQuery(thd); + } + +diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc +index 3545508..1459d7c 100644 +--- a/sql/sys_vars.cc ++++ b/sql/sys_vars.cc +@@ -2145,6 +2145,10 @@ static Sys_var_ulong Sys_join_buffer_size( + "join_buffer_size", "The size of the buffer that is used for full joins", + HINT_UPDATEABLE SESSION_VAR(join_buff_size), CMD_LINE(REQUIRED_ARG), + VALID_RANGE(128, ULONG_MAX), DEFAULT(256 * 1024), BLOCK_SIZE(128)); ++static Sys_var_uint Sys_hash_join_recursion_threshold( ++ "hash_join_test", "How many times as many rows as there are in memory to be processed should there be before triggering a recursion when the hash table is full?", ++ HINT_UPDATEABLE SESSION_VAR(hash_join_test), CMD_LINE(REQUIRED_ARG), ++ VALID_RANGE(0, UINT_MAX32), DEFAULT(0), BLOCK_SIZE(1)); + + static Sys_var_ulong Sys_hash_join_max_bloom_size( + "hash_join_max_bloom_size", "The maximum size of bloom filter used in hash join." +diff --git a/sql/system_variables.h b/sql/system_variables.h +index c9b72a7..fbf1137 100644 +--- a/sql/system_variables.h ++++ b/sql/system_variables.h +@@ -209,6 +209,7 @@ struct System_variables { + uint cte_max_recursion_depth; + ulonglong histogram_generation_max_mem_size; + ulong join_buff_size; ++ uint hash_join_test; + ulong hash_join_max_bloom_size; + ulong lock_wait_timeout; + ulong max_allowed_packet; diff --git a/patches/mysqld_log.docx b/patches/mysqld_log.docx new file mode 100644 index 0000000000000000000000000000000000000000..09338de331200bc081b655911c8ed46d95799e71 GIT binary patch literal 22903 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 literal 0 HcmV?d00001 diff --git a/patches/readme.txt b/patches/readme.txt new file mode 100644 index 000000000..22cf712f8 --- /dev/null +++ b/patches/readme.txt @@ -0,0 +1,5 @@ +atomicBloom.patch 是并行版本布隆 +bloomLog.patch 是带输出信息的哈希连接 +在当前的mysql8.0.20-bloom-filter分支上可直接使用这两个patch且相互没有冲突。 + +mysqld输出.docx 是打上bloomLog.patch之后运行哈希连接语句会产生的日志示例以及解释。 \ No newline at end of file -- Gitee From b0d65f64d6a5c293a6f75fcb523a30f2333cf2f9 Mon Sep 17 00:00:00 2001 From: b <1131188047@qq.com> Date: Fri, 9 Dec 2022 16:34:16 +0800 Subject: [PATCH 5/6] upd: new bloomsize formula & segment type --- sql/hash_join_buffer.h | 2 +- sql/hash_join_iterator.cc | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sql/hash_join_buffer.h b/sql/hash_join_buffer.h index 3d1457c7d..9c8ed89d4 100644 --- a/sql/hash_join_buffer.h +++ b/sql/hash_join_buffer.h @@ -372,7 +372,7 @@ class HashJoinRowBuffer { hash_map_iterator m_last_row_stored; // The following functions and fields are related to bloom filters - using bloom_filter_type = bloom_filter; + using bloom_filter_type = bloom_filter; const size_t m_bloom_max_mem_available; unique_ptr_destroy_only m_bloom_filter; unique_ptr_destroy_only m_hash_store; diff --git a/sql/hash_join_iterator.cc b/sql/hash_join_iterator.cc index cb9090cbf..9bc0df0de 100644 --- a/sql/hash_join_iterator.cc +++ b/sql/hash_join_iterator.cc @@ -1478,9 +1478,8 @@ bool HashJoinIterator::ConstructBloomWithHashStore() { size_t cardinality = hash_store->EstimateCardinality(); hash_store->Rewind(); - size_t bloom_size = (size_t)((-1.0) * cardinality * log(0.05) / - (log(2.0) * log(2.0))); - if(m_row_buffer.bloom_filter_init(bloom_size)){ + size_t bloom_size = (size_t)(2 * cardinality - 1); + if(m_row_buffer.bloom_filter_init(bloom_size)) { return true; } -- Gitee From f101ff7191e00e6483e8bf74e8af3c928e40df53 Mon Sep 17 00:00:00 2001 From: b <1131188047@qq.com> Date: Fri, 9 Dec 2022 16:49:19 +0800 Subject: [PATCH 6/6] upd: patch update --- patches/atomicBloom.patch | 6 +++--- patches/bloomLog.patch | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/patches/atomicBloom.patch b/patches/atomicBloom.patch index 8250bc328..b2e0e9ad9 100644 --- a/patches/atomicBloom.patch +++ b/patches/atomicBloom.patch @@ -93,15 +93,15 @@ index 0091a48..2b8a990 100644 class HyperLogLog { const double pow_2_64 = pow(2.0, 64.0); diff --git a/sql/hash_join_buffer.h b/sql/hash_join_buffer.h -index 3d1457c..22d98fd 100644 +index 9c8ed89..4256eb6 100644 --- a/sql/hash_join_buffer.h +++ b/sql/hash_join_buffer.h @@ -372,7 +372,7 @@ class HashJoinRowBuffer { hash_map_iterator m_last_row_stored; // The following functions and fields are related to bloom filters -- using bloom_filter_type = bloom_filter; -+ using bloom_filter_type = bloom_filter>; +- using bloom_filter_type = bloom_filter; ++ using bloom_filter_type = bloom_filter>; const size_t m_bloom_max_mem_available; unique_ptr_destroy_only m_bloom_filter; unique_ptr_destroy_only m_hash_store; diff --git a/patches/bloomLog.patch b/patches/bloomLog.patch index 07b8b992a..3499791d6 100644 --- a/patches/bloomLog.patch +++ b/patches/bloomLog.patch @@ -1,5 +1,5 @@ diff --git a/sql/hash_join_buffer.h b/sql/hash_join_buffer.h -index 3d1457c..e5354e4 100644 +index 9c8ed89..51ddfc7 100644 --- a/sql/hash_join_buffer.h +++ b/sql/hash_join_buffer.h @@ -391,6 +391,13 @@ class HashJoinRowBuffer { @@ -17,7 +17,7 @@ index 3d1457c..e5354e4 100644 } // namespace hash_join_buffer diff --git a/sql/hash_join_iterator.cc b/sql/hash_join_iterator.cc -index cb9090c..2f582f3 100644 +index 9bc0df0..96efb8e 100644 --- a/sql/hash_join_iterator.cc +++ b/sql/hash_join_iterator.cc @@ -55,6 +55,15 @@ @@ -58,7 +58,7 @@ index cb9090c..2f582f3 100644 +//只开递归:设置max_bloom_size=0,hash_join_test=4 +//只开布隆:设置max_bloom_size>0,hash_join_test=3 + -+static const bool countErr = true;//统计误判率。注:会造成明显的额外开销(5%+),log输出的[HashStore] read=xxx也会增大! ++static const bool countErr = false;//统计误判率。注:会造成明显的额外开销(5%+),log输出的[HashStore] read=xxx也会增大! HashJoinIterator::HashJoinIterator( THD *thd, unique_ptr_destroy_only build_input, @@ -356,16 +356,15 @@ index cb9090c..2f582f3 100644 return need_bloom; } -@@ -1477,23 +1635,39 @@ bool HashJoinIterator::ConstructBloomWithHashStore() { +@@ -1477,22 +1635,38 @@ bool HashJoinIterator::ConstructBloomWithHashStore() { ha_rows n_rows = hash_store->GetNumberOfRows(); size_t cardinality = hash_store->EstimateCardinality(); hash_store->Rewind(); + if(m_recursion_depth==0&&m_state==State::READING_ROW_FROM_PROBE_ITERATOR) + printf("[HashStore] nRows=%lld cardinality=%ld ", n_rows, cardinality); - size_t bloom_size = (size_t)((-1.0) * cardinality * log(0.05) / - (log(2.0) * log(2.0))); - if(m_row_buffer.bloom_filter_init(bloom_size)){ + size_t bloom_size = (size_t)(2 * cardinality - 1); + if(m_row_buffer.bloom_filter_init(bloom_size)) { return true; } + @@ -397,7 +396,7 @@ index cb9090c..2f582f3 100644 return false; } -@@ -1518,4 +1692,4 @@ static bool ShouldStartRecursiveJoin( +@@ -1517,4 +1691,4 @@ static bool ShouldStartRecursiveJoin( } return cost_direct > cost_recursive * 1.05; -- Gitee