CSCC2024数据库内核赛道Profile记录

news2024/11/11 10:54:32

同学参加CSCC2024数据库系统赛道比赛,我和他一起研究了一些优化的case,最后成功拿到全国2/325。在这里记录一下我们讨论优化过的问题(建议把源码下下来边读边搜代码,否则会晕)

行锁占用内存过大

Q:TPCC测试中行锁占了大量内存,疑似是glibc的malloc的内存管理有问题,生产中换个其他库比如jemalloc就能解决,但是OJ只有glibc

相关代码:

/**
 * @description: 申请行级共享锁
 * @return {bool} 加锁是否成功
 * @param {Transaction*} txn 要申请锁的事务对象指针
 * @param {Rid&} rid 加锁的目标记录ID 记录所在的表的fd
 * @param {int} tab_fd
 */
bool LockManager::lock_shared_on_record(Transaction *txn, const Rid &rid, int tab_fd) {
    std::lock_guard lock(latch_);

    if (!check_lock(txn)) {
        return false;
    }

    LockDataId lock_data_id(tab_fd, rid, LockDataType::RECORD);
    auto &&it = lock_table_.find(lock_data_id);
    if (it == lock_table_.end()) {
        it = lock_table_.emplace(std::piecewise_construct, std::forward_as_tuple(lock_data_id),
                                 std::forward_as_tuple()).first;
        it->second.oldest_txn_id_ = txn->get_transaction_id();
    }

    auto &lock_request_queue = it->second;
    for (auto &lock_request: lock_request_queue.request_queue_) {
        // 如果锁请求队列上该事务已经有共享锁或更高级别的锁(X)了,加锁成功
        if (lock_request.txn_id_ == txn->get_transaction_id()) {
            // 事务能执行到这里,要么第一次申请,要么等待结束了,拿到锁了
            assert(lock_request.granted_);
            return true;
        }
    }

    // 如果其他事务有 X 锁,加锁失败(no-wait)
    // if (lock_request_queue.group_lock_mode_ == GroupLockMode::X || lock_request_queue.group_lock_mode_ == GroupLockMode::IX || lock_request_queue.group_lock_mode_ == GroupLockMode::SIX) {
    //     lock_request_queue.cv_.notify_all();
    //     throw TransactionAbortException(txn->get_transaction_id(), AbortReason::DEADLOCK_PREVENTION);
    // }

    // 第一次申请,检查锁队列中有没有冲突的事务
    // Check for conflicting locks and apply wait-die logic
    if (lock_request_queue.group_lock_mode_ == GroupLockMode::X || lock_request_queue.group_lock_mode_ ==
        GroupLockMode::IX || lock_request_queue.group_lock_mode_ == GroupLockMode::SIX) {
        if (txn->get_transaction_id() > lock_request_queue.oldest_txn_id_) {
            throw TransactionAbortException(txn->get_transaction_id(), AbortReason::DEADLOCK_PREVENTION);
        }
        lock_request_queue.oldest_txn_id_ = txn->get_transaction_id();
        lock_request_queue.request_queue_.emplace_back(txn->get_transaction_id(), LockMode::SHARED);
        std::unique_lock<std::mutex> ul(latch_, std::adopt_lock);
        auto &&cur = lock_request_queue.request_queue_.begin();
        lock_request_queue.cv_.wait(ul, [&lock_request_queue, txn, &cur]() {
            for (auto &&it = lock_request_queue.request_queue_.begin(); it != lock_request_queue.request_queue_.end();
                 ++it) {
                if (it->txn_id_ != txn->get_transaction_id()) {
                    if (it->lock_mode_ != LockMode::SHARED || it->granted_) {
                        return false;
                    }
                } else {
                    cur = it;
                    break;
                }
            }
            return true;
        });
        cur->granted_ = true;
        lock_request_queue.group_lock_mode_ = static_cast<GroupLockMode>(std::max(
            static_cast<int>(GroupLockMode::S), static_cast<int>(lock_request_queue.group_lock_mode_)));
        ++lock_request_queue.shared_lock_num_;
        txn->get_lock_set()->emplace(lock_data_id);
        ul.release();
        return true;
    }

    // 每次事务申请锁都要更新最老事务id
    if (txn->get_transaction_id() < lock_request_queue.oldest_txn_id_) {
        lock_request_queue.oldest_txn_id_ = txn->get_transaction_id();
    }

    // 将当前事务锁请求加到锁请求队列中
    lock_request_queue.request_queue_.emplace_back(txn->get_transaction_id(), LockMode::SHARED, true);
    // 更新锁请求队列锁模式为共享锁
    lock_request_queue.group_lock_mode_ = GroupLockMode::S;
    ++lock_request_queue.shared_lock_num_;
    txn->get_lock_set()->emplace(lock_data_id);
    return true;
}

/**
 * @description: 申请行级排他锁
 * @return {bool} 加锁是否成功
 * @param {Transaction*} txn 要申请锁的事务对象指针
 * @param {Rid&} rid 加锁的目标记录ID
 * @param {int} tab_fd 记录所在的表的fd
 */
bool LockManager::lock_exclusive_on_record(Transaction *txn, const Rid &rid, int tab_fd) {
    std::lock_guard lock(latch_);

    if (!check_lock(txn)) {
        return false;
    }

    LockDataId lock_data_id(tab_fd, rid, LockDataType::RECORD);
    auto &&it = lock_table_.find(lock_data_id);
    if (it == lock_table_.end()) {
        it = lock_table_.emplace(std::piecewise_construct, std::forward_as_tuple(lock_data_id),
                                 std::forward_as_tuple()).first;
        it->second.oldest_txn_id_ = txn->get_transaction_id();
    }

    auto &lock_request_queue = it->second;
    for (auto &lock_request: lock_request_queue.request_queue_) {
        // 该事务上的锁请求队列上已经有互斥锁了,加锁成功
        if (lock_request.txn_id_ == txn->get_transaction_id()) {
            assert(lock_request.granted_);
            if (lock_request.lock_mode_ == LockMode::EXCLUSIVE) {
                return true;
            }
            // 如果当前记录没有其他事务在读,升级写锁
            if (lock_request.lock_mode_ == LockMode::SHARED && lock_request_queue.request_queue_.size() == 1) {
                lock_request.lock_mode_ = LockMode::EXCLUSIVE;
                lock_request_queue.group_lock_mode_ = GroupLockMode::X;
                lock_request_queue.shared_lock_num_ = 0;
                return true;
            }

            assert(lock_request.lock_mode_ == LockMode::SHARED);
            // 整个队列的时间戳不一定严格降序,需比较其中最老的事务id,用一个 oldest_txn_id_ 变量来维护,且等待队列中的处于等待的当前事务不可能还会申请其他锁了(阻塞)
            // 无论有没有得到锁都要先进入等待队列,得到锁后 granted_ 置真
            if (txn->get_transaction_id() > lock_request_queue.oldest_txn_id_) {
                // Younger transaction requests the lock, abort the current transaction
                throw TransactionAbortException(txn->get_transaction_id(), AbortReason::DEADLOCK_PREVENTION);
            }

            lock_request_queue.oldest_txn_id_ = txn->get_transaction_id();
            lock_request_queue.request_queue_.emplace_back(txn->get_transaction_id(), LockMode::EXCLUSIVE);
            std::unique_lock<std::mutex> ul(latch_, std::adopt_lock);
            auto &&cur = lock_request_queue.request_queue_.begin();
            // 通过条件:当前请求之前没有任何已授权的请求
            lock_request_queue.cv_.wait(ul, [&lock_request_queue, txn, &cur]() {
                for (auto &&it = lock_request_queue.request_queue_.begin();
                     it != lock_request_queue.request_queue_.end(); ++it) {
                    if (it->txn_id_ != txn->get_transaction_id()) {
                        if (it->granted_) {
                            return false;
                        }
                    } else {
                        cur = it;
                        break;
                    }
                }
                return true;
            });
            cur->granted_ = true;
            lock_request_queue.group_lock_mode_ = GroupLockMode::X;
            txn->get_lock_set()->emplace(lock_data_id);
            ul.release();
            return true;
        }
    }

    // 如果其他事务有其他锁,加锁失败(no-wait)
    if (lock_request_queue.group_lock_mode_ != GroupLockMode::NON_LOCK) {
        if (txn->get_transaction_id() > lock_request_queue.oldest_txn_id_) {
            // Younger transaction requests the lock, abort the current transaction
            throw TransactionAbortException(txn->get_transaction_id(), AbortReason::DEADLOCK_PREVENTION);
        }

        lock_request_queue.oldest_txn_id_ = txn->get_transaction_id();
        lock_request_queue.request_queue_.emplace_back(txn->get_transaction_id(), LockMode::EXCLUSIVE);
        std::unique_lock<std::mutex> ul(latch_, std::adopt_lock);
        auto &&cur = lock_request_queue.request_queue_.begin();
        // 通过条件:当前请求之前没有任何已授权的请求
        lock_request_queue.cv_.wait(ul, [&lock_request_queue, txn, &cur]() {
            for (auto &&it = lock_request_queue.request_queue_.begin(); it != lock_request_queue.request_queue_.end();
                 ++it) {
                if (it->txn_id_ != txn->get_transaction_id()) {
                    if (it->granted_) {
                        return false;
                    }
                } else {
                    cur = it;
                    break;
                }
            }
            return true;
        });
        cur->granted_ = true;
        lock_request_queue.group_lock_mode_ = GroupLockMode::X;
        txn->get_lock_set()->emplace(lock_data_id);
        ul.release();
        return true;
    }

    if (txn->get_transaction_id() < lock_request_queue.oldest_txn_id_) {
        lock_request_queue.oldest_txn_id_ = txn->get_transaction_id();
    }

    // 将当前事务锁请求加到锁请求队列中
    lock_request_queue.request_queue_.emplace_back(txn->get_transaction_id(), LockMode::EXCLUSIVE, true);
    // 更新锁请求队列锁模式为排他锁
    lock_request_queue.group_lock_mode_ = GroupLockMode::X;
    // 添加行级排他锁
    txn->get_lock_set()->emplace(lock_data_id);
    return true;
}

A:为什么lock_table_一直在emplace但没释放?

Q:lock_table_的value(LockRequestQueue)里面的request_queue(锁请求队列)是会在unlock时erase掉里面的元素的。但因为考虑同一个行(LockDataId)还可能会加锁,所以没有释放lock_table_的key

相关代码:

/**
 * @description: 释放锁
 * @return {bool} 返回解锁是否成功
 * @param {Transaction*} txn 要释放锁的事务对象指针
 * @param {LockDataId} lock_data_id 要释放的锁ID
 */
bool LockManager::unlock(Transaction *txn, const LockDataId &lock_data_id) {
    std::lock_guard lock(latch_);

    auto &txn_state = txn->get_state();
    // 事务结束,不能再解锁
    if (txn_state == TransactionState::COMMITTED || txn_state == TransactionState::ABORTED) {
        return false;
    }

    if (txn_state == TransactionState::GROWING) {
        txn_state = TransactionState::SHRINKING;
    }

    std::unordered_map<LockDataId, LockRequestQueue>::iterator it;
    std::unordered_map<IndexMeta, std::unordered_map<LockDataId, LockRequestQueue> >::iterator ii;

    if (lock_data_id.type_ == LockDataType::GAP) {
        ii = gap_lock_table_.find(lock_data_id.index_meta_);
        if (ii == gap_lock_table_.end()) {
            return true;
        }
        it = ii->second.find(lock_data_id);
        if (it == ii->second.end()) {
            return true;
        }
    } else {
        it = lock_table_.find(lock_data_id);
        if (it == lock_table_.end()) {
            return true;
        }
    }

    auto &lock_request_queue = it->second;
    auto &request_queue = lock_request_queue.request_queue_;

    auto &&request = request_queue.begin();
    for (; request != request_queue.end(); ++request) {
        if (request->txn_id_ == txn->get_transaction_id()) {
            break;
        }
    }

    if (request == request_queue.end()) {
        return true;
    }

    // 一个事务可能对某个记录持有多个锁,S,IX
    do {
        // 维护锁请求队列
        if (request->lock_mode_ == LockMode::SHARED || request->lock_mode_ == LockMode::S_IX) {
            --lock_request_queue.shared_lock_num_;
        }
        if (request->lock_mode_ == LockMode::INTENTION_EXCLUSIVE || request->lock_mode_ == LockMode::S_IX) {
            --lock_request_queue.IX_lock_num_;
        }
        // 删除该锁请求
        request_queue.erase(request);

        request = request_queue.begin();
        for (; request != request_queue.end(); ++request) {
            if (request->txn_id_ == txn->get_transaction_id()) {
                break;
            }
        }
    } while (request != request_queue.end());

    // 维护队列锁模式,为空则无锁
    // TODO 擦除锁表
    if (request_queue.empty()) {
        lock_request_queue.group_lock_mode_ = GroupLockMode::NON_LOCK;
        lock_request_queue.oldest_txn_id_ = INT32_MAX;
        // 唤醒等待的事务
        lock_request_queue.cv_.notify_all();

        // if (lock_data_id.type_ == LockDataType::GAP) {
        //     // 相交的间隙锁也得唤醒
        //     for (auto &[data_id, queue]: ii->second) {
        //         // if (queue.group_lock_mode_ != GroupLockMode::NON_LOCK) {
        //         if (lock_data_id.gap_.isCoincide(data_id.gap_)) {
        //             queue.cv_.notify_all();
        //         }
        //         // }
        //     }
        // }

        return true;
    }

    // 否则找到级别最高的锁和时间戳最小的事务
    auto max_lock_mode = LockMode::INTENTION_SHARED;
    for (auto &request: request_queue) {
        max_lock_mode = std::max(max_lock_mode, request.lock_mode_);
        if (request.txn_id_ < lock_request_queue.oldest_txn_id_) {
            lock_request_queue.oldest_txn_id_ = request.txn_id_;
        }
    }

    lock_request_queue.group_lock_mode_ = static_cast<GroupLockMode>(static_cast<int>(max_lock_mode) + 1);

    // 唤醒等待的事务
    lock_request_queue.cv_.notify_all();

    // if (lock_data_id.type_ == LockDataType::GAP) {
    //     // 相交的锁表也得唤醒
    //     for (auto &[data_id, queue]: ii->second) {
    //         // if (queue.group_lock_mode_ != GroupLockMode::NON_LOCK) {
    //         if (lock_data_id.gap_.isCoincide(data_id.gap_)) {
    //             queue.cv_.notify_all();
    //         }
    //         // }
    //     }
    // }
    return true;
}

A:现在问题是占内存,不就应该erase掉lock_table_里的东西吗(否则哈希表过大)?

Q:但erase好像并没有释放内存,只是还给内存池了

A:还给内存池就可以了啊。不释放给OS也只会导致其它程序无法分配,你自己的程序不受影响。但现在是你自己的程序内存占的过多,所以不是链接里的那个问题

实现异步写盘后速度反而更慢

异步写盘代码:

DiskScheduler::DiskScheduler(DiskManager *disk_manager) : disk_manager_(disk_manager) {
    // TODO(P1): remove this line after you have implemented the disk scheduler API
    // throw NotImplementedException(
    //      "DiskScheduler is not implemented yet. If you have finished implementing the disk scheduler, please remove the
    //      " "throw exception line in `disk_scheduler.cpp`.");

    // Spawn the background thread
    stop_thread_ = false;
    background_thread_.emplace([&] { StartWorkerThread(); });
    // background_thread_->detach();
}

DiskScheduler::~DiskScheduler() {
    // Put a `std::nullopt` in the queue to signal to exit the loop
    // for (auto &[_, req] : request_queue_) {
    //     std::ignore = _;
    //     req.Put(std::nullopt);
    // }
    stop_thread_ = true;
    if (background_thread_.has_value()) {
        if (background_thread_.value().joinable()) {
            background_thread_->join();
        }
    }
}

void DiskScheduler::Schedule(DiskRequest r) { request_queue_[r.page_id_.page_no].Put(std::make_optional(std::move(r))); }

void DiskScheduler::ScheduleRead(Page &page) {
    auto e = request_queue_[page.get_page_no()].TryReadFromQueue();
    if (e.has_value() && e.value().has_value()) {
        auto &last_req = e.value();
        memcpy(page.get_data(), last_req->data_, PAGE_SIZE);
    } else {
        disk_manager_->read_page(page.get_page_id().fd, page.get_page_id().page_no, page.get_data(), PAGE_SIZE);
        request_queue_[page.get_page_no()].LoadBuffer(DiskRequest(page.get_page_id(), page.get_data()));
    }
}

void DiskScheduler::StartWorkerThread() {
    while (!stop_thread_) {
        for (auto &[_, req] : request_queue_) {
            std::ignore = _;
            while (auto e = req.Get()) {
                if (!e.has_value()) {
                    break;
                }
                auto &r = e.value();
                disk_manager_->write_page(r.page_id_.fd, r.page_id_.page_no, r.data_, PAGE_SIZE);
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}

外部调度代码:

/**
 * @description: 更新页面数据, 如果为脏页则需写入磁盘,再更新为新页面,更新page元数据(data, is_dirty, page_id)和page table
 * @param {Page*} page 写回页指针
 * @param {PageId} new_page_id 新的page_id
 * @param {frame_id_t} new_frame_id 新的帧frame_id
 */
void BufferPoolInstance::update_page(Page *page, PageId new_page_id, frame_id_t new_frame_id) {
    // Todo:
    // 1 如果是脏页,写回磁盘,并且把dirty置为false
    // 2 更新page table
    // 3 重置page的data,更新page id
    if (page->is_dirty()) {
#ifdef ENABLE_LOGGING
        // 置换出脏页且 lsn 大于 persist 时需要刷日志回磁盘
        if (log_manager_ != nullptr && page->get_page_lsn() > log_manager_->get_persist_lsn()) {
            log_manager_->flush_log_to_disk();
        }
#endif
#ifdef ENABLE_ASYNC_DISK
        auto it = disk_scheduler_.find(page->get_page_fd());
        if (it == disk_scheduler_.end()) {
            it = disk_scheduler_.emplace(page->get_page_fd(), disk_manager_).first;
        }
        it->second.Schedule({page->get_page_id(), page->get_data()});
#else
        disk_manager_->write_page(page->get_page_id().fd, page->get_page_id().page_no, page->get_data(), PAGE_SIZE);
#endif
        page->is_dirty_ = false;
    }

    page_table_.erase(page->get_page_id());
    page_table_[new_page_id] = new_frame_id;

    // TODO new_page 需要 reset,update 不需要,因为会被覆盖
    page->reset_memory();
    page->id_ = new_page_id;
    page->pin_count_ = 0;
}

A:这个disk_scheduler_里大概有多少个元素?

Q:一个表对应一个disk_scheduler_,一个disk_scheduler_是一个线程。现在跑的这个test case是有9个表

Q:TPCC大部分跑的sql都是过索引的,buffer pool页面命中率很高,需要置换的次数很少,也就某个sql会有20次来次,其他基本0次或者个位数

A:那本来(实现异步写盘之前)是偶尔write_page卡一下,现在是九个线程一直sleep 1000,可能对主线程有点影响。建议不要构造DiskScheduler的时候就启动线程,可以等put时候再启动,比如看request数量大于多少之后再创建线程之类的,然后处理完了就关闭。也可以用信号量实现,有任务的时候让系统自动拉起线程,比较轻量,可以参考这个实现:

class AsyncIncreWriteThread : public QThread {
private:

    //单例模式,这块不需要懒加载。异步增量写入线程一定需要。
    static AsyncIncreWriteThread* asyncIncreWriteThread;

    SwitchRecordStore* recordStore;

    QSemaphore semaphore;

    int32_t batchSize;

    atomic_int32_t unInCreWriteCount;  //保证并发操作安全  信号量会有强制写入操作,而这个数据可靠

    AsyncIncreWriteThread(const int32_t& size){
        this->recordStore = new SwitchRecordStore();
        this->batchSize = size;
        this->unInCreWriteCount = 0;
    }

    //析构函数里面都是同步释放 仅在最头部控制异步
    ~AsyncIncreWriteThread(){
        MemUtil::clearPtrMem(recordStore);
    }

    void addComitInfo(const vector<pair<ResourceInfo, vector<Record*>>>& upDateInfo, const vector<pair<ResourceInfo, vector<IndexRecord*>>>& indexUpDateInfo, const vector<pair<ResourceInfo, vector<uint64_t>>>& bitMapUpDateInfo){
        const int32_t& recCount = recordStore->addComitInfo(upDateInfo, indexUpDateInfo, bitMapUpDateInfo);
        //依据的是record个数
        unInCreWriteCount.fetch_add(recCount);  //先更新个数再释放信号量
        semaphore.release(recCount);
    }

    void forceIncreWriteToDisk(){
        semaphore.release(batchSize);   //释放所需的信号量 强制开始异步写入
        while(unInCreWriteCount.load() != 0);    //非阻塞等待完成 如果本来就为0会立刻返回
    }

    bool isResUnIncreWriteToDisk(const ResourceInfo& resInfo){
        return recordStore->isResUnIncreWriteToDisk(resInfo);
    }

protected:

    virtual void run(){
        while(true){
            //这里获取信号量不严格 外界调用相关函数释放所需信号量会强制刷进磁盘
            semaphore.acquire(batchSize);
            if(unInCreWriteCount.load() == 0){ continue; }  //无需异步写入 跳过直接等待下一次异步写
            //根据记录写磁盘 并更新同步进度
            IOUtil::updateSyncProgress(recordStore->increWriteToDisk() * Record::getRecordSize());
            unInCreWriteCount.store(0);
        }
    }
}

缓冲池分片不均衡

Q:缓冲池在事务执行过程中有几千万次fetchpage的函数调用,目前我用了一把大锁来解决并发,效率比较低,profile发现缓冲池几乎不发生写盘,读盘和等锁占了比较多的时间,所以想用缓冲池分片的方法来缓解等锁的问题(原先只有一个缓冲池/一个锁,现在开成多个)。但是现在通过哈希来分配缓冲池并没有实现很好的负载均衡,比如两个缓冲池,一个缓冲池被占满了,一个缓冲池只用了1/3

相关代码:

Page *BufferPoolManager::new_page(PageId *page_id) {
    *page_id = {page_id->fd, disk_manager_->allocate_page(page_id->fd)};
    return instances_[get_instance_no(*page_id)]->new_page(page_id);
}

PageId定义:

struct PageId {
    int fd; //  Page所在的磁盘文件开启后的文件描述符, 来定位打开的文件在内存中的位置
    page_id_t page_no = INVALID_PAGE_ID;

    friend bool operator==(const PageId &x, const PageId &y) { return x.fd == y.fd && x.page_no == y.page_no; }

    friend bool operator!=(const PageId &x, const PageId &y) { return !(x == y); }

    bool operator<(const PageId &x) const {
        if (fd < x.fd) return true;
        return page_no < x.page_no;
    }

    std::string toString() {
        return "{fd: " + std::to_string(fd) + " page_no: " + std::to_string(page_no) + "}";
    }

    // inline int64_t Get() const {
    //     return (static_cast<int64_t>(fd << 16) | page_no);
    // }
};

// PageId的自定义哈希算法, 用于构建unordered_map<PageId, frame_id_t, PageIdHash>
// struct PageIdHash {
//     size_t operator()(const PageId &x) const { return (x.fd << 16) | x.page_no; }
// };

namespace std {
    template<>
    struct hash<PageId> {
        size_t operator()(const PageId &obj) const {
            // return (obj.fd << 16) | obj.page_no;
            std::size_t h1 = std::hash<int>{}(obj.fd);
            std::size_t h2 = std::hash<page_id_t>{}(obj.page_no);
            return (h1 << 1) ^ (h2);
        }
    };
}

Q:我们的场景是,fd变化很小,pageno变化很大。或许应该设计一个和pageno相关性比较大的哈希函数

A:这个是一个表一个文件(fd)的吗?

Q:对,9个表9个fd,表有大有小

A:现在这个哈希函数是h2左移之后都变成偶数了,所以如果%2的话,hash结果奇偶是否均匀就看fd的奇偶是否均匀。如果是少量大表多数小表,且大表的fd是偶数,那就是第一个pool的元素多

Bison ast::SemValue拷贝占用大量时间

A:分析了一下,速度慢是因为这个解析器是所有连接共享的,所以总在等待锁。需要改成一个连接(一个线程)一个解析器。首先对flex和bison的配置做修改,主要是需要加%option reentrant和%param {void *xx}这两个配置:

这样改完之后,生成的解析器函数里都会多出来一个yyscanner参数,不同线程传不同的这个就行了:

每个线程一个scanner之后,parse_tree也需要改成多个:

Q:快了不少,但是火焰图几乎没变?

A:火焰图统计的是次数,不是时间

Q:每隔相同时间抽样一次,抽样数量也可以反映时间吧

A:开了多线程之后单次采样给它加的数量多了,所以实际时间和图里的数量应该是随线程数有个倍数关系的

源码仓库

Kosthi/RMDB-2024: CSCC2024数据库管理系统赛道 一等奖参赛作品 (2/325) (github.com)

同学正在找工作!!哪位正在招数据库系统研发快去私信他嗷!!他狠厉害的 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2139565.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图说GPT网络结构(参数量与计算量估计)

现在AI领域的主流模型几乎都是Transformer网络架构衍生而来。大热的LLM中的生成类模型很多都是来自于Transformer的变体&#xff0c;即decoder only架构。而GPT就是该类中的经典模型。尽管现在变体甚多&#xff0c;但大多没有根本性地改变其套路。 为了阐述方便&#xff0c;首…

Java开发与实现教学管理系统动态网站

博主介绍&#xff1a;专注于Java .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的可以…

资源管理新视角:利用 FastAPI Lifespan 事件优化你的应用II

本文说明在 FastAPI 应用程序中使用 lifespan 事件来管理资源的加载和卸载。lifespan 事件允许你在应用启动时执行一些初始化代码&#xff0c;并在应用关闭时执行一些清理代码。这是通过使用异步上下文管理器实现的&#xff0c;具体来说&#xff0c;是通过 asynccontextmanager…

自由流转--实例

一、自由流转的形态 流转能力打破设备界限&#xff0c;多设备联动&#xff0c;使用户应用程序可分可合、可流转&#xff0c;实现如邮件跨设备编辑、多设备协同健身、多屏游戏等分布式业务。 二、跨端迁移 在应用开发层面&#xff0c;跨端迁移指在A端运行的UIAbility迁移到B端上…

店群合一模式下的社区团购新发展——结合链动 2+1 模式、AI 智能名片与 S2B2C 商城小程序源码

摘要&#xff1a;本文探讨了店群合一的社区团购平台在当今商业环境中的重要性和优势。通过分析店群合一模式如何将互联网社群与线下终端紧密结合&#xff0c;阐述了链动 21 模式、AI 智能名片和 S2B2C 商城小程序源码在这一模式中的应用价值。这些创新元素的结合为社区团购带来…

四、(JS)JS中常见的加载事件

一、文档加载监听 &#xff08;1&#xff09;抛出疑惑&#xff0c;什么是文档加载监听&#xff1f;为什么要有这个东西&#xff1f; 老样子&#xff0c;我们先讲一个场景&#xff0c;带着大家熟悉为什么会有文档加载监听&#xff0c;是来解决什么问题来着的。 我们先看下这段…

无心剑七绝《中秋相思》

七绝中秋相思 中秋月满意深长 百代江阳老窖香 莫道天涯情不尽 相思寸寸赋华章 2023年9月29日 平水韵七阳平韵 这首诗七绝《中秋相思》由无心剑所作&#xff0c;以其深情的笔触描绘了中秋夜的相思之情。 诗中首句“中秋月满意深长”即以中秋圆月为起点&#xff0c;勾勒出了一幅…

相同的数--(力扣)

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true示例 2&#xff1a; 输入…

C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿&…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 9月16日,星期一

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年9月16日 星期一 农历八月十四 1、 台风“贝碧嘉”来袭&#xff0c;上海、杭州发文提醒市民非必要不外出&#xff1b;上海市内高速全部封闭。 2、 中秋假期第一天最热门目的地出炉&#xff1a;北京、成都、上海包揽前三。 …

下一代 推荐系统:多智能体 + 深度强化学习,充分利用文本信息,更深刻的理解用户真实需求和兴趣点

下一代 AI 医疗&#xff1a;知识图谱RAG 多智能体 大模型 推荐&#xff0c;是否有必要&#xff1f;方案一&#xff1a;基于内容的 Agent 推荐方案二&#xff1a;多智能体深度强化学习 推荐强化学习&#xff1a;看、干、想的过程多智能体框架 方案三&#xff1a;在家慢病自管…

【VSCode】VSCode Background 背景插件辅助窗口程序

前排贴上Github项目链接 GitHub窗口项目链接 这是一个基于VSCode上由shalldie上传的background扩展制作的windows窗口程序。 该程序旨在通过窗口程序尽可能的完善该扩展原有的功能。 background - shalldie 的最大优势是我目前仅在其扩展上发现了UseFront的选项&#xff0c;这…

基于AgentUniverse在金融场景中的多智能体应用探索

基于AgentUniverse在金融场景中的多智能体应用探索 1.基于大模型智能体超级状态机 智能体、多智能体都是当下的技术热点,但作为一个技术人应该理解,所有的技术都有自己所针对的问题、及其能力边界,并不存在普适的、放诸业务场景皆 work 的技术方案。在这里尝试区分,从大模…

Cubieboard2(四) 系统构建 —— 基于 mainline

文章目录 1 环境准备1.1 编译环境准备1.2 主线源码准备1.2.1 linux 源码1.2.2 U-Boot 源码1.2.3 启动文件 boot.scr1.2.4 根文件系统 rootfs 2 内核编译2.1 添加 tenda aic8800 驱动&#xff08;可选&#xff09;2.2 编译 linux 内核 3 U-Boot 编译4 分区与烧录4.1 sdcard 分区…

Pocketpair澄清表示《幻兽帕鲁》无意转型免费游戏

有报道称&#xff0c;Pocketpair的首席执行官沟部拓郎接受采访暗示&#xff0c;该工作室的热门生存游戏《幻兽帕鲁》可能很快会转为免费游戏&#xff0c;以“延长其寿命”。该工作室现在澄清说&#xff0c;这次采访是几个月前进行的&#xff0c;目前不再有这样的计划。 “我们…

[产品管理-19]:NPDP新产品开发 - 17 - 产品设计与开发工具 - 实体化设计工具:联合分析、功能分析、FAST技术图和逆向工程

目录 前言&#xff1a; 一、什么是实体化设计 1.1 什么是实体化设计 1、定义与概述 2、设计流程 3、关键要素 4、应用领域 5、举例说明 1.2 实体化设计与概念设计的区别 实体化设计 概念设计 区别归纳 1.3 实体化设计与初步设计、规格设计的区别 1、定义与目的 …

Matlab simulink建模与仿真 第十四章(信号输出库)

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 一、信号输出库中的模块概览 注&#xff1a;部分模块在第二章中有介绍&#xff0c;本章不再赘述。 二、文件及工作空间模块 1、To File文件模块 &#xff08;1&#xff09;在MATLAB中可用MAT文件对工作区的…

[数据集][目标检测]无人机识别检测数据集VOC+YOLO格式6986张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;6986 标注数量(xml文件个数)&#xff1a;6986 标注数量(txt文件个数)&#xff1a;6986 标注…

理解Android开发中的MVC、MVVM和MVP设计模式

全篇大概1600 字&#xff0c;建议阅读时间10分钟。 引言 在 Android 应用开发中&#xff0c;设计模式是帮助开发者构建结构清晰、可维护性高的应用程序的关键工具。MVC&#xff08;Model-View-Controller&#xff09;、MVVM&#xff08;Model-View-ViewModel&#xff09;和 MV…

基于双向RRT算法的三维空间最优路线规划matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 单向RRT算法 4.2 双向RRT算法 5.完整程序 1.程序功能描述 基于双向RRT&#xff08;Randomly Exploring Random Trees, 随机探索随机树&#xff09;算法的三维空间最优路径规划是一种解…