9.9 KiB
9.9 KiB
DateItemModel 改进分析
1. 当前 DateItemModel 使用分析
1.1 结构定义
struct DateItemModel {
QString datetimePattern;
int monthsOffset = 0;
int daysOffset = 0;
QStringList dataMap; // unused
QStringList daysMap;
QStringList monthsMap;
QMap<int, QString> yearsMap;
QStringList weeksMap;
QStringList hoursMap;
static DateItemModel createDefaultReplaceModel();
static void loadFromXml(DateItemModel& vm, const QXmlStreamAttributes& attrs);
static void saveToXml(const DateItemModel& vm, QXmlStreamAttributes& attrs);
};
1.2 主要使用场景
PicDateItem 类:
- 存储为成员变量
m_model - 通过
setModel(DateItemModel model)方法接收新模型(使用移动语义) - 在
setCustomAttrs和saveCustomAttrs中进行 XML 序列化 - 用于生成实时更新的日期时间字符串
DateReplaceEditor 类:
- 在 UI 编辑器中双向绑定数据
- 通过
dateModel()返回完整的模型数据 - 通过
setDateModel(const DateItemModel& model)接收模型
DateVarSet 类:
- 与 UI 界面进行双向数据同步
- 在
dateModel()和setDateModel()中进行数据转换
2. 性能分析 (针对全志H618平台)
2.1 平台特性
- 全志H618是ARM架构处理器,主频约1.5GHz
- 通常配备1-2GB RAM
- 资源相对受限,需要优化内存和CPU使用
2.2 QStringList vs std::array 性能对比
2.2.1 内存占用对比
-
QStringList: 每个列表包含额外的元数据(引用计数、大小、容量等)
- dataMap (16个QString): ~16*(QString开销) + 指针数组开销
- daysMap (31个QString): ~31*(QString开销) + 指针数组开销
- monthsMap (12个QString): ~12*(QString开销) + 指针数组开销
- weeksMap (7个QString): ~7*(QString开销) + 指针数组开销
- hoursMap (24个QString): ~24*(QString开销) + 指针数组开销
-
std::array<QString, N>: 直接内联存储,无额外指针
- 更好的缓存局部性
- 减少内存碎片
2.2.2 访问性能
- QStringList: 间接访问(指针跳转)
- std::array: 直接访问(连续内存),更好的缓存命中率
2.2.3 构造/析构性能
- QStringList: 共享数据,浅拷贝,但在赋值时可能触发深度拷贝
- std::array: 需要移动/拷贝每个元素
2.3 移动语义分析
2.3.1 当前使用模式
在 PicDateItem::setModel 中:
void PicDateItem::setModel(DateItemModel model)
{
m_model = std::move(model); // 显式使用移动语义
}
在 DateItemModel::createDefaultReplaceModel 中:
return model; // 依赖 NRVO/RVO 或移动语义
2.3.2 性能影响评估
对于小规模数组(N < 32):
- QStringList 移动: O(1) - 只需转移指针和引用计数
- std::array<QString, N> 移动: O(N) - 需要移动 N 个 QString
但要注意:
- QString 本身实现了移动语义,移动是高效的操作
- N 值较小(最大31个元素),移动开销可接受
- 在 H618 上,移动 31 个 QString 的时间仍远小于毫秒级
3. 实际使用场景分析
3.1 频繁操作类型
- 读取操作: 高频(UI 显示、日期计算)
- 写入操作: 中频(用户编辑、配置变更)
- 复制/移动操作: 低频(对象传递、赋值)
3.2 性能瓶颈预测
- 读取操作:
std::array胜出(更好的缓存局部性) - 写入操作: 两者相当
- 移动操作:
QStringList略优,但差距很小
4. 推荐方案
4.1 最优实现
#include <array>
struct DateItemModel {
QString datetimePattern;
int monthsOffset = 0;
int daysOffset = 0;
std::array<QString, 16> dataMap; // 固定大小,类型安全
std::array<QString, 31> daysMap; // 固定大小,类型安全
std::array<QString, 12> monthsMap; // 固定大小,类型安全
std::array<QString, 7> weeksMap; // 固定大小,类型安全
std::array<QString, 24> hoursMap; // 固定大小,类型安全
std::array<QString, 16> yearsArray; // 年份数组,固定大小
int startYear; // 起始年份,用于计算索引对应的年份
};
4.2 实现注意事项
// 在 ViewModel.cpp 中相应修改
DateItemModel DateItemModel::createDefaultReplaceModel()
{
DateItemModel model;
// 初始化 dataMap
const char* dataValues[] = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
for (int i = 0; i < 16; ++i) {
model.dataMap[i] = QString(dataValues[i]);
}
// 初始化 daysMap
for (int i = 0; i < 31; ++i) {
model.daysMap[i] = QString("%1").arg(i + 1, 2, 10, QChar('0'));
}
// 初始化 monthsMap
for (int i = 0; i < 12; ++i) {
model.monthsMap[i] = QString("%1").arg(i + 1, 2, 10, QChar('0'));
}
// 初始化 weeksMap
const char* weekValues[] = {"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
for (int i = 0; i < 7; ++i) {
model.weeksMap[i] = QString(weekValues[i]);
}
// 初始化 hoursMap
for (int i = 0; i < 24; ++i) {
model.hoursMap[i] = QString("%1").arg(i, 2, 10, QChar('0'));
}
int startYear = QDate::currentDate().year();
for (int i = 0; i < 16; ++i) {
model.yearsMap.insert(startYear + i, QString::number(startYear + i));
}
return model;
}
5. 性能权衡结论
5.1 移动语义影响
在全志H618平台上:
- 移动开销: 对于固定小数组,移动 31 个 QString 的开销非常小(微秒级)
- 访问优势:
std::array提供更好的缓存局部性和访问性能 - 总体影响: 类型安全和访问性能的收益远大于移动语义的轻微损失
5.2 最终建议
推荐使用 std::array,理由如下:
- 类型安全: 编译时确保固定大小,防止越界
- 性能优势: 更好的缓存局部性和访问性能
- 内存效率: 减少内存碎片,更紧凑的布局
- 可维护性: 清晰表达数据结构的固定大小特征
- 移动性能: 在实际应用场景中,移动开销可忽略不计
在资源受限的嵌入式环境(如H618)中,std::array 的内存布局优势和缓存友好性比微小的移动性能损失更重要。
6. 关于2.2KB结构体大小的分析
6.1 大小计算详情
改为 std::array 后的 DateItemModel 总大小约为:
QString datetimePattern: ~24 字节int monthsOffset: 4 字节int daysOffset: 4 字节std::array<QString, 16> dataMap: 16 × ~24 = ~384 字节std::array<QString, 31> daysMap: 31 × ~24 = ~744 字节std::array<QString, 12> monthsMap: 12 × ~24 = ~288 字节QMap<int, QString> yearsMap: ~32 字节std::array<QString, 7> weeksMap: 7 × ~24 = ~168 字节std::array<QString, 24> hoursMap: 24 × ~24 = ~576 字节
总计:约 2220 字节(~2.2KB)
6.2 性能影响评估
在全志H618平台上:
- 复制时间估算:2.2KB 复制约需 1-5 微秒
- 使用频率:UI 编辑操作每秒最多几次,非常低频
- CPU 占用:即使每秒 100 次复制,也仅占 CPU 时间的 0.05%
6.3 可接受性结论
2.2KB 结构体复制在当前代码中是完全可以接受的,因为:
- 性能影响微乎其微:复制时间远小于其他系统操作(UI渲染、I/O等)
- 使用频率极低:实际复制操作非常不频繁
- 硬件能力充足:全志H618足以处理这种规模的数据复制
- 代码简洁性:值语义比智能指针语义更简单直观
- 调试便利性:值对象更容易调试和跟踪
7. 关于createDefaultReplaceModel函数的改进
随着我们将DateItemModel结构改为使用构造函数初始化,我们也可以移除createDefaultReplaceModel静态函数。这个函数的功能可以由默认构造函数完全替代:
// 旧方式
DateItemModel model = DateItemModel::createDefaultReplaceModel();
// 新方式
DateItemModel model; // 使用默认构造函数
这样做的好处:
- 简化了API:不再需要单独的工厂函数
- 保持了一致性:所有实例都通过构造函数初始化
- 减少了代码冗余:构造函数已经提供了相同的初始化逻辑
8. 关于传递方式的分析
8.1 当前传递方式
在 PicDateItem::setModel(DateItemModel model) 中,使用值传递但配合移动语义:
void PicDateItem::setModel(DateItemModel model)
{
m_model = std::move(model); // 实际使用移动语义
}
7.2 传递方式对比
| 方式 | 复制开销 | 复杂性 | 适用场景 |
|---|---|---|---|
| 值传递 + 移动 | 低(移动而非复制) | 低 | 推荐,适合当前场景 |
| const 引用 | 无传参开销 | 中 | 需要额外复制到成员 |
| 智能指针 | 8字节指针 | 高 | 需要共享数据时 |
9. 年份映射的优化改进
原来的 QMap<int, QString> yearsMap 被替换为 std::array<QString, 16> yearsArray 和 int startYear 的组合:
优点:
- 内存效率更高:
std::array比QMap占用更少内存 - 访问速度更快:数组访问比哈希表访问更快
- 缓存友好:连续内存布局提供更好的缓存性能
- 类型安全:编译时确定大小,防止越界
实现方式:
startYear存储起始年份(如 2023)yearsArray[0]对应startYear年(2023年)yearsArray[1]对应startYear + 1年(2024年)- 以此类推,最多支持16个连续年份
访问逻辑:
// 访问特定年份的映射值
int year = 2025;
if (year >= startYear && year < startYear + 16) {
int index = year - startYear;
QString mappedValue = yearsArray[index];
}
7.3 推荐方案
保持当前的值传递设计,利用移动语义获得最佳性能:
// 调用时使用移动
DateItemModel model = DateItemModel::createDefaultReplaceModel();
picItem.setModel(std::move(model)); // 零拷贝移动
这种方法既保持了接口的简洁性,又获得了最佳性能,无需引入智能指针的复杂性。