V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  wxf666  ›  全部回复第 1 页 / 共 27 页
回复总数  530
1  2  3  4  5  6  7  8  9  10 ... 27  
11 小时 0 分钟前
回复了 freewind 创建的主题 数据库 请教多标签查询怎么做效率高?
@yjhatfdu2 #19 我用 SQLite 试了下,感觉用普通 SQL ,也勉强能满足这个需求。

## 结果

- 数据:4000 标签,1 亿数据,50 标签/数据(即,倒排索引中,约 50 亿数据 ID )
- 性能:0.8s 查询一个标签,拥有的 625W 数据 ID ,升序输出
- 体积:倒排索引表,约 11 GB

## 环境

- SYS:Win11
- CPU:i7-4790
- MEM:16 GB ,1600 MHz
- HDD:顺序读 150 MB/s ,4K 随机读 0.65 MB/s

## 多标签呢?

SQLite 不支持 Sort Merge Join ,多个标签一起查时,很慢。。
需要物化每个标签的结果,再由 SQLite 进行布隆匹配、B 树二分查找。。

(测前,都用 RamMap 清除了,系统对文件的缓存)
2 个 625W 数据 ID 的标签,结果 250W ,耗时 4.9 秒,
3 个 625W 数据 ID 的标签,结果 205W ,耗时 8.5 秒,
4 个 625W 数据 ID 的标签,结果 120W ,耗时 11.9 秒。。

不知道能不能用 Virtual Table ,写个表值函数(知道有,没写过),自己实现:

- Sort Merge Join 的效果,流式匹配所有标签,免除物化表、上千万次 B 树匹配的开销
- 提速读取数据 ID ( SQL 中,若写为 `SELECT COUNT(*)`,不计算具体数据 ID ,会提速到 0.1s )

## 倒排索引结构

- 表结构:`(<标签 ID ,高位数据 ID >,低 11 位数据 ID 集合 BLOB )`
- 若 > 146 个低位数据 ID ,第 3 列视为 293 字节的 Bitmap ,但每字节最高位弃用( SQLite 中没有便捷转换 128~255 为单字节 BLOB 的方法)
- 否则,每个低位数据 ID ,转为二字节 UTF-8 字符串( 0~127 需后补 `x'00'`),并串联起来。

## 测试数据

- 标签 ID:`[0, 3999]`
- 数据 ID:`[1, 1e8]`
- 每个数据拥有的标签 ID:`[(数据 ID * 1) % 4000, (数据 ID * 2) % 4000, ..., (数据 ID * 50) % 4000]`
- 标签 ID 为 400 、1600 、2800 、3600 时,拥有的数据 ID 最多,都为 625W 个

## 查询 SQL

```sql
-- V 站吞空格,缩进改为全角空格了
WITH
   t1 AS MATERIALIZED (
     SELECT (data_hi_id << 11) |
       CASE WHEN i.stop = 7 THEN -- 低位数据 ID 量 <= 146 ,逐 2 字节翻译
         IFNULL(unicode(substr(data_lo_ids, j.value, 2)), 0)
       WHEN j.stop & (1 << (6 + j.value)) THEN -- Bitmap ,逐 bit 翻译
         i.value + j.value - 1
       END AS data_id
     FROM tag_data
     JOIN generate_series(7, IIF(length(data_lo_ids) < 293, 1, 293) * 7, 7) i
     JOIN generate_series(
       IIF(i.stop > 7, -6, 1),
       IIF(i.stop > 7, unicode(substr(data_lo_ids, i.value / 7, 1)), length(data_lo_ids)),
       IIF(i.stop > 7, 1, 2)) j
     WHERE tag_id = '《第一个标签 ID 》'
    -- 这句加了好慢。。怀疑是反复计算 data_id 导致
    -- AND data_id IS NOT NULL
  ),
  -- 以下结构类似,省略了
   t2 AS (...),
   t3 AS (...),
   t4 AS (...)
-- 写成 COUNT(*) 且仅 t1 时,飞快
-- 估计是不用算具体 data_id 了
SELECT COUNT(data_id)
FROM t1
JOIN t2 USING(data_id)
JOIN t3 USING(data_id)
JOIN t4 USING(data_id)
-- 匹配到后面,预计数据量很少时,可采用这种方式:
/*
WHERE EXISTS (
   SELECT 1
   FROM tag_data
   WHERE tag_id = '《第四个标签 ID 》'
   AND data_hi_id = data_id >> 11
   AND CASE WHEN octet_length(data_lo_ids) <= 292 THEN
       instr(data_lo_ids, IIF(data_id & 0x780, char(data_id & 0x7FF), char(data_id & 0x7FF, 0)))
     ELSE
       unicode(substr(data_lo_ids, (data_id & 0x7FF) / 7 + 1, 1)) & (1 << (data_id & 0x7FF) % 7)
     END)
*/
```

## 增删改

用触发器实现。但很慢。。

C++ 新人,正好练练手,生成倒排索引库(这都要花十几分钟。。):

```c++
// 同上,缩进是全角空格
#include <string>
#include <vector>
#include <iostream>
#include <string_view>
#include "sqlite_modern_cpp.h"

constexpr size_t MAX_DATA_ID = 1e8;
constexpr size_t MAX_TAG_COUNT = 50;
constexpr size_t MAX_TAG_ID = 4000 - 1;
constexpr size_t MAX_MEMORY = 8ull << 30;
constexpr auto DB_PATH = "D:/out.db";

int main() {

   struct TagData {
     struct {
       uint8_t data_lo_ids[(2048 + 6) / 7]{};
    } data_hi_ids[(MAX_DATA_ID >> 11) + 1]{};
  };

   constexpr int tag_steps = MAX_MEMORY / sizeof(TagData);

   sqlite::database db(DB_PATH);
   db << "CREATE TABLE IF NOT EXISTS tag_data ("
     "   tag_id INT,"
     "   data_hi_id INT,"
     "   count INT,"
     "   data_lo_ids BLOB,"
     "   PRIMARY KEY (tag_id, data_hi_id)"
     ") WITHOUT ROWID";

   std::string blob_buf;
   std::vector<uint16_t> data_lo_ids;
   auto insert_stmt = db << "INSERT INTO tag_data VALUES (?, ?, ?, CAST(? AS BLOB))";

   for (size_t tag_id_beg = 0; tag_id_beg <= MAX_TAG_ID; tag_id_beg += tag_steps) {

     auto tag_id_end = (std::min)(tag_id_beg + tag_steps - 1, MAX_TAG_ID);
     auto tags = std::make_unique<TagData[]>(tag_id_end - tag_id_beg + 1);

     db << "BEGIN";
     std::cout
      << "正在计算 1~" << MAX_DATA_ID << ",每个数的 1~" << MAX_TAG_COUNT << " 倍,÷" << MAX_TAG_ID + 1
      << " 后的余数,是否在 [" << tag_id_beg << ", " << tag_id_end << "] 范围内……" << std::endl;

     for (size_t data_id = 1; data_id <= MAX_DATA_ID; data_id++) {
       for (size_t times = 1; times <= MAX_TAG_COUNT; times++) {
         size_t tag_id = (data_id * times) % (MAX_TAG_ID + 1);
         if (tag_id >= tag_id_beg && tag_id <= tag_id_end)
           tags[tag_id - tag_id_beg]
            .data_hi_ids[data_id >> 11]
            .data_lo_ids[(data_id & 0x7FF) / 7] |= 1 << ((data_id & 0x7FF) % 7);
      }
    }

     for (size_t tag_id = tag_id_beg; tag_id <= tag_id_end; tag_id++) {

       auto& tag = tags[tag_id - tag_id_beg];
       std::cout << "正在写入 tag_id = " << tag_id << " 至数据库……\r";

       for (auto& data_hi: tag.data_hi_ids) {
         data_lo_ids.clear();
         for (size_t i = 0; i < sizeof data_hi.data_lo_ids; i++)
           if (data_hi.data_lo_ids[i])
             for (size_t j = 0; j < 7; j++)
               if (data_hi.data_lo_ids[i] & (1 << j))
                 data_lo_ids.push_back(i * 7 + j);

         if (data_lo_ids.size() > 292 / 2)
           insert_stmt
            << tag_id << (&data_hi - tag.data_hi_ids) << data_lo_ids.size()
            << std::string_view((char *) data_hi.data_lo_ids, sizeof data_hi.data_lo_ids)
            >> []{};

         else if (!data_lo_ids.empty()) {
           blob_buf.clear();
           for (auto lo_id: data_lo_ids)
             if (lo_id > 127)
               blob_buf.append({char(0xC0 | (lo_id >> 6)), char(0x80 | (lo_id & 0x3F))});
             else
               blob_buf.append({char(lo_id), 0});
           insert_stmt
            << tag_id << (&data_hi - tag.data_hi_ids) << data_lo_ids.size() << blob_buf
            >> []{};
        }
      }
    }

     db << "COMMIT";
  }
}
```
5 天前
回复了 yunv2 创建的主题 程序员 关于 cpu 性能和 Java 编译速度的问题
@liprais #32 和楼主问的并不 100% 相关,只是有些联系。

想问的就是,Java 编译应该已经足够快了,还这么在意这个干啥?

就好比,现在 CPU 内存 硬盘,都很强了,结果还有人天天在意,PC 软件那几 KB 体积啥的。。

见到这种情况,你不会产生疑问吗? https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
5 天前
回复了 yunv2 创建的主题 程序员 关于 cpu 性能和 Java 编译速度的问题
不懂就问,Java 编译很复杂吗?不是简单翻译到字节码而已吗?

复杂的应该都在 JIT 时吧?搜集信息,实时优化,编译成 CPU 指令啥的?


那应该能参考 TinyCC 的编译速度?毕竟这货也不怎么优化代码,效率和 gcc -O0 相当。。

即,二十年前的 奔腾 4 CPU ,能(单线程)每秒编译 100W 行 C99 代码成 x86 指令?


那 Java 项目即使有过亿行代码,现代 8 核电脑,最多不也就等待 10 秒钟,就能编译完成了吗?

楼主为啥那么在意呢。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
6 天前
回复了 freewind 创建的主题 数据库 请教多标签查询怎么做效率高?
@yjhatfdu2 #15 是 (tag_id, main_ids_array) 这样存的吗?

即,有 4000 条记录,总计 50 亿个主表 ID (平均每条记录有 125W 个主表 ID )?

每个 ID 有 4 字节,算下来也要 (5e9 * 4) / (1 << 20) = 18.6 GB 呀。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
7 天前
回复了 freewind 创建的主题 数据库 请教多标签查询怎么做效率高?
@yjhatfdu2 #8 这个索引,实际有多大呢?

我怀疑它是按照 (array_value, id) 来存的,即存了 50 亿条。

每条 10 字节的话,也要快 50 GB 了。。
7 天前
回复了 yodhcn 创建的主题 数据库 数据库中间表要不要用联合主键?
@waytodelay #4

如果中间表字段少(比如就存 user_id 、group_id ),存索引不是还多一个自增字段吗?

如果中间表字段多,存索引的话,会不会大概率经常回表,查其他字段呢?

另外,索引相当于乱序插入了,页分裂肯定很频繁呀。。

(除非你严格按照 user_id 、group_id 自增顺序插入。那你的业务,是没有回头客啥的吗?)
7 天前
回复了 yodhcn 创建的主题 数据库 数据库中间表要不要用联合主键?
你用自增主键,表本身是不会页分裂。

但联合索引呢?你也严格按 (userId, groupId) 递增顺序,插入到表里吗?

你的业务请求,挺有规律啊。。https://i.imgur.com/krir4IG.png
@Int100 #10 要不,你模拟几个场景出来看看?比如:

10GB 的 A.json (格式是 [{"a": xxx, "b": yyy, ...}, ...],约一亿条),
100GB 的 B.json (格式是 [{"c": xxx, "d": yyy, ...}, ...],约十亿条),

需求一:A 里的 a, b 字段去 B 里的 c, d 查,保留结果是 xxx 的,再分组取前一万,再分季度汇总,。。。
需求二:。。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
17 天前
回复了 Braisdom 创建的主题 推广 产品文档上线啦!
@Braisdom #16 不是概括总结数据为人话,是用人话输入想查询的结果,程序翻译成 SQL 执行?
17 天前
回复了 Braisdom 创建的主题 推广 产品文档上线啦!
都用上 AI 了,为嘛不直接翻译人话,出查询结果呢?
@NessajCN #23 这么久以来,有通过自己查,避免了什么东西吗?

想看看有没有必要,尽量少用别人编译好的二进制。。

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
@debuggerx #13 看了看 AvaloniaUI 的展示案例,确实手机端很少,只有两个。界面看起来还行。。

QT 我一时也想不到,手机上的应用有啥。。

所以你觉得 AI 的能力边界是什么呢?

其他人也能写出赚这么多钱的,那就直接让他《写一个机器人,能日种几亩田》之类的。。?

https://i.imgur.com/krir4IG.png https://i.imgur.com/krir4IG.png
@debuggerx #11 QT 、AvaloniaUI ,这些不行吗?

AI 出来后,让它《写一个日赚过万的 App 》,会咋样呢?

换句话说,AI 的能力边界是什么呢?

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
@kuanat #26 浏览器会利用系统的 DWM ,由其来单独合成普通页面与视频画面,而不是浏览器自己合成再提交?

放到 Windows 上说,就是利用了 DirectComposition ?

可这是 Win8 才引入的呀。。Win7- 是咋做到的呢?

https://i.imgur.com/F29pmQ6.png https://i.imgur.com/F29pmQ6.png
把设备晾在那一天,看看语音助手耗多少电、用多少流量,不就心中有数了嘛。。
25 天前
回复了 keakon 创建的主题 Redis Garnet 真比 Redis 快吗?
@hez2010 #8 如果 8 线程访问 Redis ,每个线程 pipeline 都是 1 。这算啥?
28 天前
回复了 test123abc 创建的主题 程序员 PHP 处理百万级 execl
为嘛不用 DuckDB 呢?它支持直接读取 xlsx 呀。。

速度上,之前的 1BRC (十亿行文本)挑战里,它能做到(普通人 SQL 写法)计算只用 5 秒钟。

或者,23 亿词 13GB 的英文维基文本,计算 TOP 1000 高频词,i5-8250U 轻薄本上,单线程下,只需 3 分钟,500MB 内存。。

如果你用 PHP 不能做到,更快更省资源,用用它也不错呀。。

反正我用 Java 一般写法,都要 6 分钟,2GB 内存。。

用 C++ 来写,也才勉强和它打平。。

https://i.imgur.com/krir4IG.png https://i.imgur.com/krir4IG.png
32 天前
回复了 jaween 创建的主题 MySQL 哪里有 sql 的练习可以做
《 SQL 解惑(第二版)》,里面有 75 个问题,并用 SQL 编程解决。

跟着做完,SQL 能力肯定能提升很多。。
34 天前
回复了 lulinchuanllc 创建的主题 前端开发 网页内存占用高如何优化
什么浏览器,七八百兆就崩啊?我这常年几个 G 呢。。

不会是一个页面就七八百兆吧?抖音客户端,也是 Electron ,都才四五百兆啊。。

https://i.imgur.com/veWihk6.png https://i.imgur.com/veWihk6.png
35 天前
回复了 MegatronKing 创建的主题 推广 买断制上线,一天的营收超过一个月
Deepin 20 ,打开时报错说 libstdc++.so.6 版本太低诶。。

```
/usr/share/reqable/reqable: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by /usr/share/reqable/lib/libdesktop_multi_window_plugin.so)
/usr/share/reqable/reqable: /lib/x86_64-linux-gnu/libstdc++.so.6: version `GLIBCXX_3.4.26' not found (required by /usr/share/reqable/lib/libreqable_appdump_plugin.so)
```
1  2  3  4  5  6  7  8  9  10 ... 27  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4955 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 44ms · UTC 07:45 · PVG 15:45 · LAX 00:45 · JFK 03:45
Developed with CodeLauncher
♥ Do have faith in what you're doing.