记一次表格数据排序优化(一)--排序30000条数据有多卡
目录
需求
第一次尝试
运行环境
思路
存储
排序
触发排序操作
如何实现高效的排序
关键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_qstrlststd::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);elsevSortDescending(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 = "^";elseqstrOrder = "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;elsem_arrOrder[iCol] = Qt::AscendingOrder;m_iMajorCol = iCol;//然后排序,排序结果保存在m_vecOrderif((Qt::SortOrder)(m_arrOrder[iCol]) == Qt::AscendingOrder)vSortAscending(m_vec_qstrlst, iCol);elsevSortDescending(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_vecOrderstd::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_vecOrderstd::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秒。
运行模式,排序时间降到约三秒
相关文章:
记一次表格数据排序优化(一)--排序30000条数据有多卡
目录 需求 第一次尝试 运行环境 思路 存储 排序 触发排序操作 如何实现高效的排序 关键1 关键2 关键3 磨刀不误砍柴工 关键4 代码 效果 卡顿原因分析 原因1 原因2 第二次尝试 需求 1 我的qt程序通过表格显示30000条数据。数据来自udp,udp每隔10秒…...
图形渲染中的定点数和浮点数
三种API的NDC区别 NDC全称,Normalized Device Coordinates Metal、Vulkan、OpenGL的区别如下: featureOpenGL NDCMetal NDCVulkan NDC坐标系右手左手右手z值范围[-1,1][0,1][0,1]xy视口范围[-1,1][-1,1][-1,1] GPU渲染的定点数和浮点数 定点数类型&a…...
【深度学习】CNN简述
文章目录 一、卷积神经网络(CNN)二、CNN结构特性1. CNN 典型结构2. 局部连接3. 权重共享4.空间或时间上的次采样 三、理解层面 一、卷积神经网络(CNN) 卷积神经网络(Convolutional Neural Network,CNN)是一种用于处理…...
强化学习课程:stanford_cs234 学习笔记(3)introduction to RL
文章目录 前言7 markov 实践7.1 markov 过程再叙7.2 markov 奖励过程 MRP(markov reward process)7.3 markov 价值函数与贝尔曼方程7.4 markov 决策过程MDP(markov decision process)的 状态价值函数7.4.1 状态价值函数7.4.2 状态…...
紫檀博物馆一游与软件开发
今天去逛了中国紫檀博物馆,里边很多层展品,也有一些清代的古物,檀木,黄花梨木家具和各种摆件,馆主陈丽华女士也是发心复原、保留和弘扬中国的传统文化,和西游记唐僧扮演者迟成瑞先生一家。 每一件展品都精…...
RocketMQ初认识
ProducerCustomerNameServer: Broker的注册服务发现中心BrokerServer:主要负责消息的存储、投递和查询以及服务高可用保证 RocketMQ的集群部署: 单个master的分支多个Master 模式:集群中有多个 Master 节点,彼此之间相互独立。生产者可以将消…...
第十三章:持久化存储_《凤凰架构:构建可靠的大型分布式系统》
第十三章 持久化存储 一、Kubernetes存储设计核心概念 (1)存储抽象模型 PersistentVolume (PV):集群级别的存储资源抽象(如NFS卷/云存储盘)PersistentVolumeClaim (PVC):用户对存储资源的声明请求&#…...
Chrome开发者工具实战:调试三剑客
在前端开发的世界里,Chrome开发者工具就是我们的瑞士军刀,它集成了各种强大的功能,帮助我们快速定位和解决代码中的问题。今天,就让我们一起来看看如何使用Chrome开发者工具中的“调试三剑客”:断点调试、调用栈跟踪和…...
教程:如何使用 JSON 合并脚本
目录 1. 介绍 2. 使用方法 3. 注意事项 4. 示例 5.完整代码 1. 介绍 该脚本用于将多个 COCO 格式的 JSON 标注文件合并为一个 JSON 文件。COCO 格式常用于目标检测和图像分割任务,包含以下三个主要部分: "images":图像信息&a…...
C++/Qt 模拟sensornetwork的工作
C/Qt 可视化模拟sensornetwork的工作 C/Qt 模拟sensornetwork的工作 C/Qt 可视化模拟sensornetwork的工作内容简介(一) 需求和规格说明(1)问题描述(2)设计目的(3)基本要求࿰…...
ffmpeg音频分析
对一个16k 单声道音频,生成频谱图 ./ffmpeg -i input.wav -lavfi "showspectrumpics800x400:modecombined:scalelin:gain1.5" spectrum.png...
【多线程】CAS机制
目录 一. CAS的概念 二. CAS的原理 三.标准库中的CAS 四. CAS的应用 (1)原子类的使用 (2) 自旋锁的实现 五. CAS的ABA问题 一. CAS的概念 CAS(Compare And Swap)机制是一种无锁的并发控制技术&#…...
音视频(四)android编译
前言 前面已经讲了在windows上应用了,这章主要讲述android上编译 1:环境 git 如果失败 直接跑到相应网站 手动下载 ubuntu22.* android ndk r21e download:https://developer.android.google.cn/ndk/downloads/index.html?hluk 为什么用这个࿰…...
Chapter07_图像压缩编码
文章目录 图像压缩编码图像压缩编码基础图像压缩的基本概念信息相关信息冗余信源编码及其分类 图像编码模型信源编码器模型信源解码器模型 数字图像的信息熵信源符号码字平均长度信息熵信息量 变长编码费诺码霍夫曼编码 位平面编码格雷码 图像压缩编码 数字图像的压缩是指在满…...
团体设计程序天梯赛L2-025 # 分而治之
文章目录 题目解读输入格式输出格式 思路Ac Code参考 题目解读 在战争中,我们希望首先攻下敌方的部分城市,使其剩余的城市变成孤立无援,然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序,判断每个方案的可…...
Linux网络套接字
Socket 编程 UDP 本章将函数介绍和代码编写实战一起使用。 IPv4 的 socket 网络编程,sockaddr_in 中的成员 struct in_addr.sin_addr 表示 32 位 的 IP 地址 但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示和in_addr 表示之间转换; 字符串转 in_addr…...
看爬山虎学本领 软爬机器人来创新 各种场景能适应
*本文只做阅读笔记分享* 一、灵感来源:向植物取经 大家好!今天来聊一款超酷的软爬机器人,它的灵感来自会攀爬的植物——爬山虎。 大家都知道,爬墙高手爬山虎能在各种复杂墙面轻松生长攀爬,可现有的攀爬机器人在复杂…...
1-Docker安装
1.准备环境 1.第一步:创建以自己的姓名全拼的用户名 [roothadoop ~]# useradd qiwenyong [roothadoop ~]# passwd qiwenyong Changing password for user qiwenyong. New password: BAD PASSWORD: The password is shorter than 7 characters Retype new passwor…...
WPS JS宏编程教程(从基础到进阶)-- 第三部分:JS宏编程语言开发基础
第三部分:JS宏编程语言开发基础 @[TOC](第三部分:JS宏编程语言开发基础)**第三部分:JS宏编程语言开发基础**1. 变量与数据类型**变量声明:三种方式****示例代码****数据类型判断****实战:动态处理单元格类型**2. 运算符全解析**算术运算符****易错点:字符串拼接 vs 数值相…...
BT-Basic函数之首字母T
BT-Basic函数之首字母T 文章目录 BT-Basic函数之首字母Ttabtesttest conttest monitortest on boardstest scanworkstest shortstesthead cleanuptesthead configurationtesthead istesthead power on/offtesthead statustestjet print level istestordertestplan generationth…...
经典算法 约数之和
原题目链接 问题描述 假设现在有两个自然数 A 和 B,设 S 为 A^B 的所有约数之和。 请你计算:S mod 9901 的值。 输入格式 在一行中输入两个用空格隔开的整数 A 和 B。 输出格式 输出一个整数,表示 S mod 9901 的值。 数据范围 0 ≤ A, …...
Flinksql--订单宽表
参考: https://chbxw.blog.csdn.net/article/details/115078261 (datastream 实现) 一、ODS 模拟订单表及订单明细表 CREATE TABLE orders (order_id STRING,user_id STRING,order_time TIMESTAMP(3),-- 定义事件时间及 Watermark(允许5秒乱序&#x…...
C# 窗体应用(.FET Framework ) 打开文件操作
一、 打开文件或文件夹加载数据 1. 定义一个列表用来接收路径 public List<string> paths new List<string>();2. 打开文件选择一个文件并将文件放入列表中 OpenFileDialog open new OpenFileDialog(); // 过滤 open.Filter "(*.jpg;*.jpge;*.bmp;*.png…...
极客天成NVFile:无缓存直击存储性能天花板,重新定义AI时代并行存储新范式
在AI算力需求呈指数级爆发的今天,存储系统正面临一场前所未有的范式革命。传统存储架构中复杂的缓存机制、冗余的数据路径、僵化的扩展能力,已成为制约千卡GPU集群算力释放的重要因素。极客天成NVFile并行文件存储系统以全栈并行化架构设计和无缓存直通数…...
Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解
N皇后问题要求在NN的棋盘上放置N个皇后,使得她们无法互相攻击。本文提供递归和循环迭代两种解法,并通过图示解释核心逻辑。 一、算法核心思想 使用回溯法逐行放置皇后,通过冲突检测保证每行、每列、对角线上只有一个皇后。发现无效路径时回退…...
【代码艺廊】pyside6桌面应用范例:homemade-toolset
在研发测试日常工作中,通常会遇到很多琐碎的事情,占用我们工作的时间和精力,从而导致我们不能把大部分的注意力放在主要的工作上面。为了解决这个问题,除了加人之外,我们通常会开发一些日常用的效率工具,比…...
LeetCode 3047 求交集区域内的最大正方形面积
探寻矩形交集中的最大正方形面积 在算法与数据结构的探索之路上,二维平面几何问题一直占据着独特的地位,它们不仅考验我们的空间思维能力,还要求我们能够巧妙地运用算法逻辑。今天,我们将深入剖析一道极具代表性的二维平面几何算…...
谷歌开源单个 GPU 可运行的Gemma 3 模型,27B 超越 671B 参数的 DeepSeek
自从 DeepSeek 把训练成本打下来之后,各个模型厂家现在不再堆参数进行模型的能力对比。而是转向了训练成本优化方面,且还要保证模型能力不减反增的效果。包括使用较少的模型参数,降低 GPU 使用数量,降低模型内存占用等等技术手段。…...
C++_类和对象(下)
【本节目标】 再谈构造函数Static成员友元内部类匿名对象拷贝对象时的一些编译器优化再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 class Date { public:Date(in…...
《Java实战:素数检测算法优化全解析——从暴力枚举到筛法进阶》
文章目录 摘要一、需求分析二、基础实现代码与问题原始代码(暴力枚举法)问题分析 三、优化版代码与解析优化1:平方根范围剪枝优化2:偶数快速跳过完整优化代码 四、性能对比五、高阶优化:埃拉托斯特尼筛法算法思想代码实…...
