记一次表格数据排序优化(一)--排序30000条数据有多卡

news2025/5/13 7:55:07

目录

需求

第一次尝试

运行环境

思路

存储

排序

触发排序操作

如何实现高效的排序

关键1

关键2

关键3 磨刀不误砍柴工

关键4

代码

效果

卡顿原因分析

原因1

原因2

第二次尝试


需求

1 我的qt程序通过表格显示30000条数据。数据来自udp,udp每隔10秒发一次,一次含两个包。每包含有15000个数据。

2 每条数据有三个属性,在表格中通过3列显示这三个属性。其中一个属性是区分“左”和“右”的。udp发送的两个包,一个包全是“左”属性,另一个包全是"右"属性。

3 当用户点击三列中某一列的表头时,触发程序针对该列对应的属性进行排序。排序又分升序和降序。假如某列上一次使用升序排列,那么当用户再次点击该列表头时,该列变为降序排列。

4 假如当前针对表格的第一列排序,当用户点击第二列表头后,表格就改为采用第二列排序,第一列排序作废。

5 udp发来的新数据将覆盖上一次的旧数据。假如覆盖前,表格采用第三列排序,收到新数据后,程序自动调用排序算法针对新数据的第三列排序。

第一次尝试

下面涉及的所有函数、变量都出自同一套代码。代码在我的资源【免费】qt表格排序示例,使用mv结构资源-CSDN文库中下载。

运行环境

win10 vs2019 + Qt6.4.3 debug编译 

思路

存储

采用MV(model-view)结构。表格数据保存于Model中。Model是QAbstractTableModel的派生类。这种MV结构的使用在我前面的博客《MV结构下设置Qt表格的代理》已经介绍了。在本例中,实际数据保存在成员变量m_vec_qstrlst之中,类型是std::vector<QStringList>。vector的每一个元素是QStringList类型,代表一行的内容。每一行的具体内容按列依次排在QStringList的每个元素里面。

表格显示什么内容由QAbsractTableModel::data()决定。

排序

触发排序操作

点击表头将触发信号QHeaderView::sectionClicked,通过此信号触发排序操作即可。

如何实现高效的排序
关键1

排序通过函数vSortByColumn()实现。这个函数根据变量m_arrOrder进一步调用函数vSortAscending或者vSortDescending,分别完成升序排序和降序排序。

前面提到了,真正的数据保存在m_vec_qstrlst之中。注意,排序不能直接作用于m_vec_qstrlst。原因是m_vec_qstrlst的每个元素都是QStringList,是比较复杂的类型。排序时,后面的元素排到前面,前面的元素排到后面,将导致频繁的QStringList析构和构造,拉低效率

解决问题的方法有两个:

1 m_vec_qstrlst里面不要直接存放QStringList,而是使用指针。移动指针不会触发构造和析构。如果你担心指针造成内存泄漏,可以使用智能指针。

2 另外建立一个成员变量m_vecOrder,类型为std::vector<int>。元素个数与m_vec_qstrlst的元素个数相同,第0个元素=0,第1个元素=1,...最后一个元素=m_vec_qstrlst.size()-1。排序时,不修改m_vec_qstrlst的次序,而是对m_vecOrder的次序进行重排。由于m_vecOrder的元素类型是int,所以排序带来的开销远远小于直接对m_vec_qstrlst排序。

本例采用第二种做法。

关键2

具体排序时,我也有两个选择:

1 C++标准库提供排序函数sort(),复杂度为O(nlog(n))。

2 从八种排序算法中选择一个,自己用C++实现。

这里我选择手写冒泡算法实现排序。后面的博客会看到,手写的效率是低于使用std::sort函数的。

关键3 磨刀不误砍柴工

从直觉上说,我只要从m_vec_qstrlst里面提取两个相邻的QStringList,根据排序的列号来获取对应在QStringList中的元素,然后转化为int 或者float就能比较了。但这样涉及大量的容器访问,以及字符串到数值型的转化,开销也很大。更何况冒泡排序的复杂度是O(n^2)。所以我在执行冒泡排序之前,先遍历m_vec_qstrlst,把待比较的那一列数据专门提取出来,存入数组vecVal,通过vecVal来执行排序。遍历m_vec_qstrlst的复杂度是O(n),相比O(n^2),我还是赚的。此外,遍历后,产生的新vector结构比原始容器简单,可以降低cache-miss,更有利于提高效率

关键4

release 模式比debug模式快。同为debug编译的程序,运行模式又比调试模式快。后面会提到。

代码

这里只给出核心的Model代码。完整代码及测试数据从我的资源【免费】qt表格排序示例,使用mv结构资源-CSDN文库下载

#ifndef MODELFREQDETECT_H
#define MODELFREQDETECT_H

#include <QAbstractTableModel>

class ModelFreqDetect : public QAbstractTableModel
{
    Q_OBJECT
public:
    ModelFreqDetect(QStringList, int, int, QObject * parent = 0);

    int iAddRows(bool bLeft, std::vector<QStringList> &);
    void                                vSortByColumn(int iCol);
protected:
    int rowCount(const QModelIndex &) const override;

    int columnCount(const QModelIndex &) const override;

    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
    const       int                     m_cnst_iLeftSize;
    const       int                     m_cnst_iRightSize;
    //数据保存在m_vec_qstrlst
    std::vector<QStringList>            m_vec_qstrlst;
    QStringList                         m_qstrlstHeader;
private:
    QScopedArrayPointer<int>            m_arrOrder;
    int                                 m_iMajorCol;
    const int                           m_cnst_iMaxRowCount;//表格最大行数,本示例中是30000
    //m_vecOrder的元素是m_vec_qstrlst中每个成员的序号。元素的相对次序决定了排序结果
    std::vector<int>                    m_vecOrder;
    void                                vSortDescending(std::vector<QStringList> & vecInput, int);
    void                                vSortAscending(std::vector<QStringList> & vecInput, int);
};
#endif // MODELFREQDETECT_H
#include "ModelFreqDetect.h"



ModelFreqDetect::ModelFreqDetect(QStringList qstrlst, int iLeftSize, int iRightSize, QObject * parent): QAbstractTableModel(parent),
    m_qstrlstHeader(qstrlst), m_iMajorCol(2), m_cnst_iLeftSize(iLeftSize),
    m_cnst_iRightSize(iRightSize),
    m_arrOrder(new int[m_qstrlstHeader.size()]), m_cnst_iMaxRowCount(iLeftSize + iRightSize),
    m_vec_qstrlst(iLeftSize + iRightSize)
{
    m_vecOrder.resize(0);
    for(int k = 0; k < m_qstrlstHeader.size(); k++)
    {
        m_arrOrder[k] = Qt::AscendingOrder;
    }
}

int ModelFreqDetect::iAddRows(bool bLeft, std::vector<QStringList> & vec_qstrlst)
{
    //m_vec_qstrlst并不是手工排序后的结果,而是采用默认排序
    //所谓默认排序,指的是左结果排在右前面。每个包内部的次序按照udp发来的次序
    int iLoc = bLeft ? 0 : m_cnst_iLeftSize;
    //假如送来的vec_qstrlst代表左阵面信息,则从m_vec_qstrlst.begin()开始覆盖;
    //否则就是代表右信息,从m_vec_qstrlst.begin()开始跳过m_cnst_iLeftSize,覆盖右
    std::copy(vec_qstrlst.begin(), vec_qstrlst.end(), m_vec_qstrlst.begin() + iLoc);

    if(m_iMajorCol > -1 && m_iMajorCol < m_qstrlstHeader.size())
    {
        //排序
        if((Qt::SortOrder)(m_arrOrder[m_iMajorCol]) == Qt::AscendingOrder)
            vSortAscending(m_vec_qstrlst, m_iMajorCol);
        else
            vSortDescending(m_vec_qstrlst, m_iMajorCol);
    }

    emit dataChanged(createIndex(0, 0), createIndex(m_cnst_iMaxRowCount - 1, m_qstrlstHeader.size() - 1));

    return vec_qstrlst.size();
}


int ModelFreqDetect::rowCount(const QModelIndex & parent) const
{
    return m_cnst_iMaxRowCount;
}

int ModelFreqDetect::columnCount(const QModelIndex & parent) const
{
    return m_qstrlstHeader.size();
}

QVariant ModelFreqDetect::data(const QModelIndex &index, int role) const
{
    int i = index.row(), j = index.column();
    if ((i >= 0) && (i < qMin(m_cnst_iMaxRowCount, (const int)(m_vecOrder.size()))))
    {
        if(role == Qt::DisplayRole)
        {
            if (0 <= j && j < m_qstrlstHeader.size())
            {
                QStringList qstrlst = m_vec_qstrlst.at(i);
                if(qstrlst.size() > j)
                {
                    //return qstrlst.at(j);
                    return m_vec_qstrlst.at(m_vecOrder.at(i)).at(j);
                }
            }
        }
        else
        {

        }
    }
    else if(role == Qt::TextAlignmentRole)
        return Qt::AlignCenter;
    else
    {

    }

    return QVariant();
}

//ok
QVariant ModelFreqDetect::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role == Qt::DisplayRole)
    {
        if(orientation == Qt::Horizontal)
        {
            if(section < m_qstrlstHeader.size())
            {
                QString qstrOrder;
                if(section == m_iMajorCol)
                {
                    if(Qt::AscendingOrder == m_arrOrder[m_iMajorCol])
                        qstrOrder = "^";
                    else
                        qstrOrder = "v";
                }
                else
                {
                    qstrOrder = "";
                }
                return m_qstrlstHeader.at(section) + qstrOrder;
            }
        }
        else
        {
            return QString("%1").arg(section);
        }
    }
    else if(role == Qt::TextAlignmentRole)
    {
        return Qt::AlignCenter;
    }
    else
    {

    }

    return QAbstractTableModel::headerData(section, orientation, role);
}

void ModelFreqDetect::vSortByColumn(int iCol)
{
    if(Qt::AscendingOrder == m_arrOrder[iCol])
    {
        m_arrOrder[iCol] = Qt::DescendingOrder;
    }
    else if(Qt::DescendingOrder == m_arrOrder[iCol])
        m_arrOrder[iCol] = Qt::AscendingOrder;
    else
        m_arrOrder[iCol] = Qt::AscendingOrder;
    m_iMajorCol = iCol;

    //然后排序,排序结果保存在m_vecOrder
    if((Qt::SortOrder)(m_arrOrder[iCol]) == Qt::AscendingOrder)
        vSortAscending(m_vec_qstrlst, iCol);
    else
        vSortDescending(m_vec_qstrlst, iCol);
    //更新界面
    emit dataChanged(createIndex(0, 0), createIndex(m_cnst_iMaxRowCount - 1, m_qstrlstHeader.size() - 1));
}

void ModelFreqDetect::vSortDescending(std::vector<QStringList> & vecInput, int iCol)
{
    //冒泡排序
    //1 先把用来排序的那一列的数据提取出来,并且无效的序号不要计入m_vecOrder
    std::vector<float> vecVal;
    std::vector<int> vecIdx;
    for(int k = 0; k < vecInput.size(); k++)
    {
        if(vecInput.at(k).isEmpty())
        {
            vecVal.push_back(0);
        }
        else
        {
            vecIdx.push_back(k);
            vecVal.push_back(iCol == 0 ? (vecInput.at(k).at(iCol).compare("left")) : vecInput.at(k).at(iCol).toFloat());
        }
    }

    //2 排序
    int iTail = vecIdx.size()-1;
    while(iTail > 0)
    {
        for(int k = 0; k < iTail; k++)
        {
            float f1 = vecVal.at(vecIdx.at(k));
            float f2 = vecVal.at(vecIdx.at(k+1));
            if(f1 < f2)
            {
                //默认是降序
                int iTmp = vecIdx.at(k);
                vecIdx[k] = vecIdx[k+1];
                vecIdx[k+1] = iTmp;
            }
        }
        iTail--;
    }

    m_vecOrder.swap(vecIdx);
}

void ModelFreqDetect::vSortAscending(std::vector<QStringList> & vecInput, int iCol)
{
    //冒泡排序
    //1 先把用来排序的那一列的数据提取出来,并且无效的序号不要计入m_vecOrder
    std::vector<float> vecVal;
    std::vector<int> vecIdx;
    for(int k = 0; k < vecInput.size(); k++)
    {
        if(vecInput.at(k).isEmpty())
        {
            vecVal.push_back(0);
        }
        else
        {
            vecIdx.push_back(k);
            vecVal.push_back(iCol == 0 ? (vecInput.at(k).at(iCol).compare("left")):vecInput.at(k).at(iCol).toFloat());
        }
    }

    //2 排序
    int iTail = vecIdx.size()-1;
    while(iTail > 0)
    {
        for(int k = 0; k < iTail; k++)
        {
            float f1 = vecVal.at(vecIdx.at(k));
            float f2 = vecVal.at(vecIdx.at(k+1));
            if(f1 > f2)
            {
                //默认是降序
                int iTmp = vecIdx.at(k);
                vecIdx[k] = vecIdx[k+1];
                vecIdx[k+1] = iTmp;
            }
        }
        iTail--;
    }

    m_vecOrder.swap(vecIdx);
}

效果

下面的视频展示了点击第三列的表头之后,界面卡顿大约10秒才完成排序的现象:

30000条数据排序,很卡顿。优化后的对比视频稍后上传

测试数据见【免费】排序测试数据,分两部分,一个是属性为左,另一个属性为右两者各15000条数据资源-CSDN文库 

卡顿原因分析

原因1

没有使用std::sort.下一篇博客,我将使用std::sort来排序,并与本篇的结果做对比。

原因2

刚才的测试运行在调试模式下。

在运行模式下,程序运行更快

第二次尝试

仍然使用同一套在debug下产生的程序,唯一的区别是在运行模式下执行。排序耗时大约3秒。

运行模式,排序时间降到约三秒

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

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

相关文章

图形渲染中的定点数和浮点数

三种API的NDC区别 NDC全称&#xff0c;Normalized Device Coordinates Metal、Vulkan、OpenGL的区别如下&#xff1a; featureOpenGL NDCMetal NDCVulkan NDC坐标系右手左手右手z值范围[-1,1][0,1][0,1]xy视口范围[-1,1][-1,1][-1,1] GPU渲染的定点数和浮点数 定点数类型&a…

【深度学习】CNN简述

文章目录 一、卷积神经网络&#xff08;CNN&#xff09;二、CNN结构特性1. CNN 典型结构2. 局部连接3. 权重共享4.空间或时间上的次采样 三、理解层面 一、卷积神经网络&#xff08;CNN&#xff09; 卷积神经网络(Convolutional Neural Network&#xff0c;CNN)是一种用于处理…

强化学习课程:stanford_cs234 学习笔记(3)introduction to RL

文章目录 前言7 markov 实践7.1 markov 过程再叙7.2 markov 奖励过程 MRP&#xff08;markov reward process&#xff09;7.3 markov 价值函数与贝尔曼方程7.4 markov 决策过程MDP&#xff08;markov decision process&#xff09;的 状态价值函数7.4.1 状态价值函数7.4.2 状态…

紫檀博物馆一游与软件开发

今天去逛了中国紫檀博物馆&#xff0c;里边很多层展品&#xff0c;也有一些清代的古物&#xff0c;檀木&#xff0c;黄花梨木家具和各种摆件&#xff0c;馆主陈丽华女士也是发心复原、保留和弘扬中国的传统文化&#xff0c;和西游记唐僧扮演者迟成瑞先生一家。 每一件展品都精…

RocketMQ初认识

ProducerCustomerNameServer: Broker的注册服务发现中心BrokerServer:主要负责消息的存储、投递和查询以及服务高可用保证 RocketMQ的集群部署&#xff1a; 单个master的分支多个Master 模式&#xff1a;集群中有多个 Master 节点&#xff0c;彼此之间相互独立。生产者可以将消…

Chrome开发者工具实战:调试三剑客

在前端开发的世界里&#xff0c;Chrome开发者工具就是我们的瑞士军刀&#xff0c;它集成了各种强大的功能&#xff0c;帮助我们快速定位和解决代码中的问题。今天&#xff0c;就让我们一起来看看如何使用Chrome开发者工具中的“调试三剑客”&#xff1a;断点调试、调用栈跟踪和…

C++/Qt 模拟sensornetwork的工作

C/Qt 可视化模拟sensornetwork的工作 C/Qt 模拟sensornetwork的工作 C/Qt 可视化模拟sensornetwork的工作内容简介&#xff08;一&#xff09; 需求和规格说明&#xff08;1&#xff09;问题描述&#xff08;2&#xff09;设计目的&#xff08;3&#xff09;基本要求&#xff0…

ffmpeg音频分析

对一个16k 单声道音频&#xff0c;生成频谱图 ./ffmpeg -i input.wav -lavfi "showspectrumpics800x400:modecombined:scalelin:gain1.5" spectrum.png

【多线程】CAS机制

目录 一. CAS的概念 二. CAS的原理 三.标准库中的CAS 四. CAS的应用 &#xff08;1&#xff09;原子类的使用 &#xff08;2&#xff09; 自旋锁的实现 五. CAS的ABA问题 一. CAS的概念 CAS&#xff08;Compare And Swap&#xff09;机制是一种无锁的并发控制技术&#…

音视频(四)android编译

前言 前面已经讲了在windows上应用了&#xff0c;这章主要讲述android上编译 1&#xff1a;环境 git 如果失败 直接跑到相应网站 手动下载 ubuntu22.* android ndk r21e download:https://developer.android.google.cn/ndk/downloads/index.html?hluk 为什么用这个&#xff0…

Chapter07_图像压缩编码

文章目录 图像压缩编码图像压缩编码基础图像压缩的基本概念信息相关信息冗余信源编码及其分类 图像编码模型信源编码器模型信源解码器模型 数字图像的信息熵信源符号码字平均长度信息熵信息量 变长编码费诺码霍夫曼编码 位平面编码格雷码 图像压缩编码 数字图像的压缩是指在满…

团体设计程序天梯赛L2-025 # 分而治之

文章目录 题目解读输入格式输出格式 思路Ac Code参考 题目解读 在战争中&#xff0c;我们希望首先攻下敌方的部分城市&#xff0c;使其剩余的城市变成孤立无援&#xff0c;然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序&#xff0c;判断每个方案的可…

Linux网络套接字

Socket 编程 UDP 本章将函数介绍和代码编写实战一起使用。 IPv4 的 socket 网络编程,sockaddr_in 中的成员 struct in_addr.sin_addr 表示 32 位 的 IP 地址 但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示和in_addr 表示之间转换; 字符串转 in_addr…

看爬山虎学本领 软爬机器人来创新 各种场景能适应

*本文只做阅读笔记分享* 一、灵感来源&#xff1a;向植物取经 大家好&#xff01;今天来聊一款超酷的软爬机器人&#xff0c;它的灵感来自会攀爬的植物——爬山虎。 大家都知道&#xff0c;爬墙高手爬山虎能在各种复杂墙面轻松生长攀爬&#xff0c;可现有的攀爬机器人在复杂…

极客天成NVFile:无缓存直击存储性能天花板,重新定义AI时代并行存储新范式

在AI算力需求呈指数级爆发的今天&#xff0c;存储系统正面临一场前所未有的范式革命。传统存储架构中复杂的缓存机制、冗余的数据路径、僵化的扩展能力&#xff0c;已成为制约千卡GPU集群算力释放的重要因素。极客天成NVFile并行文件存储系统以全栈并行化架构设计和无缓存直通数…

Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解

N皇后问题要求在NN的棋盘上放置N个皇后&#xff0c;使得她们无法互相攻击。本文提供递归和循环迭代两种解法&#xff0c;并通过图示解释核心逻辑。 一、算法核心思想 使用回溯法逐行放置皇后&#xff0c;通过冲突检测保证每行、每列、对角线上只有一个皇后。发现无效路径时回退…

谷歌开源单个 GPU 可运行的Gemma 3 模型,27B 超越 671B 参数的 DeepSeek

自从 DeepSeek 把训练成本打下来之后&#xff0c;各个模型厂家现在不再堆参数进行模型的能力对比。而是转向了训练成本优化方面&#xff0c;且还要保证模型能力不减反增的效果。包括使用较少的模型参数&#xff0c;降低 GPU 使用数量&#xff0c;降低模型内存占用等等技术手段。…

C++_类和对象(下)

【本节目标】 再谈构造函数Static成员友元内部类匿名对象拷贝对象时的一些编译器优化再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 class Date { public:Date(in…

Docker设置代理

目录 前言创建代理文件重载守护进程并重启Docker检查代理验证 前言 拉取flowable/flowable-ui失败&#xff0c;用DaoCloud源也没拉下来&#xff0c;不知道是不是没同步。索性想用代理拉镜像。在此记录一下。 创建代理文件 创建docker代理配置 sudo mkdir -p /etc/systemd/s…

一键自动备份:数据安全的双重保障

随着数字化时代的到来&#xff0c;数据已成为企业和个人不可或缺的核心资产。在享受数据带来的便捷与高效的同时&#xff0c;数据丢失的风险也随之增加。因此&#xff0c;备份文件的重要性不言而喻。本文将深入探讨备份文件的重要性&#xff0c;并介绍两种实用的自动备份方法&a…