结合案例来说明软件开发中性能优化的个人经验之二

前言

前两天刚刚写了一篇关于性能优化的文章,描述了针对一个功能点的优化历程以及进一步优化的一些思路,这几天出差,晚上也闲来无事,然后跟同事就把优化思路实现了一把,但发现了之前的推论并不正确,在这里聊一下这个问题。

前情回顾

针对数据导入(N个)这一工功能,里面有一个关键步骤是数据从数十个区域(R个)中做空间匹配寻找所处的区域,我们上次讨论了三个版本的优化,如下所示:

  • 第一个版本是直接插入版本
  • 第二个版本是批量插入版本再修改
  • 第三个版本是批量(B个)点依次匹配区域+批量插入版本

最后提出了第四个版本的改进思路就是先批量插入,用区域来批量匹配数据,再做批量修改所属区域。这么做的依据是,V3版需要进行N次R个区域的空间匹配,而V4版需要进行N/B*R次的B个点的空间匹配。(N>>R,B>R)。推测至少有着一半以上的优化效果,甚至可能是数量级级别的。

Show me the code

V3代码


    @Override
    public void transformCSV(Path filePath) {
        //删除以前的雷电数据
        flushDb();
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath.toFile())));
            CSVParser parser = CSVFormat.DEFAULT.parse(reader);
            Iterator<CSVRecord> iterator = parser.iterator();
            //跳过表头
            if (iterator.hasNext()) {
                iterator.next();
            }

            List<List<String>> csvList = new ArrayList<>();
            while (iterator.hasNext()) {
                CSVRecord record = iterator.next();
                //每行的内容
                List<String> value = new ArrayList<>();
                for (int j = 0; j < record.size(); j++) {
                    value.add(record.get(j));
                }
                if (csvList.size() >= BATCH_SIZE) {
                    thunderBoltDataFilter(csvList);
                    csvList.clear();
                }
                csvList.add(value);
            }
            thunderBoltDataFilter(csvList);

        } catch (FileNotFoundException e) {
            log.error("临时文件未找到", e);
        } catch (Exception e) {

            log.error("未定义异常", e);
            flushDb();
        }

    }

    private void flushDb() {
        remove(new QueryWrapper<>());
    }

    private List<OriginalThunderbolt> loadDataFromCSV(List<List<String>> csvList) {


        List<OriginalThunderbolt> originalThunderboltList = new ArrayList<>();

        for (List<String> csvString : csvList) {
            OriginalThunderbolt originalThunderbolt = new OriginalThunderbolt();

            originalThunderbolt.setId(SnowFlakeIdGenerator.getInstance().nextId());

            originalThunderbolt.setCode(csvString.get(0));

            if (StringUtils.isNotEmpty(csvString.get(1))) {
                Long time = TimeUtils.stringToDateLong(csvString.get(1));
                originalThunderbolt.setTime(time);
            }

            originalThunderbolt.setType(csvString.get(2));
            originalThunderbolt.setHeight(Double.valueOf(csvString.get(3)));
            originalThunderbolt.setStrength(Double.valueOf(csvString.get(4)));
            originalThunderbolt.setLatitude(Double.valueOf(csvString.get(5)));
            originalThunderbolt.setLongitude(Double.valueOf(csvString.get(6)));
            originalThunderbolt.setProvinces(csvString.get(7));
            originalThunderbolt.setCities(csvString.get(8));
            originalThunderbolt.setCounties(csvString.get(9));
            originalThunderbolt.setLocationMode(csvString.get(10));
            originalThunderbolt.setSteepness(csvString.get(11));
            originalThunderbolt.setDeviation(csvString.get(12));
            originalThunderbolt.setLocatorNumber(csvString.get(13));

            originalThunderbolt.setStatus("未同步");
            originalThunderbolt.setCreateTime(System.currentTimeMillis());
            originalThunderbolt.setUpdateTime(System.currentTimeMillis());
            originalThunderbolt.setDeleted(false);

            originalThunderboltList.add(originalThunderbolt);

        }

        return originalThunderboltList;
    }

    private QueryWrapper<Thunderbolt> buildWrapper(ThunderboltPageModel thunderboltPageModel) {
        QueryWrapper<Thunderbolt> wrapper = new QueryWrapper<>();

        if (StringUtils.isNotEmpty(thunderboltPageModel.getForestryBureauName())) {
            wrapper.likeRight("forestry_bureau", thunderboltPageModel.getForestryBureauName());
        }
        if (StringUtils.isNotEmpty(thunderboltPageModel.getForestryBureauName())) {
            wrapper.likeRight("forest_farm", thunderboltPageModel.getForestryBureauName());
        }
        if (StringUtils.isNotEmpty(thunderboltPageModel.getCode())) {
            wrapper.likeRight("code", thunderboltPageModel.getCode());
        }
        if (StringUtils.isNotEmpty(thunderboltPageModel.getType())) {
            wrapper.eq("type", thunderboltPageModel.getType());
        }
        if (StringUtils.isNotEmpty(thunderboltPageModel.getLocatorNumber())) {
            wrapper.likeRight("locator_number", thunderboltPageModel.getLocatorNumber());
        }
        if (StringUtils.isNotEmpty(thunderboltPageModel.getStatus())) {
            wrapper.eq("status", thunderboltPageModel.getStatus());
        }
        if (thunderboltPageModel.getDiscoverStartTime() != null) {
            wrapper.ge("time", thunderboltPageModel.getDiscoverStartTime());
        }
        if (thunderboltPageModel.getDiscoverEndTime() != null) {
            wrapper.le("time", thunderboltPageModel.getDiscoverEndTime());
        }

        wrapper.orderByDesc("update_time");

        wrapper.eq(DELETED_COLUMN, DEFAULT_STATUS);

        return wrapper;
    }

    private void thunderBoltDataFilter(List<List<String>> csvList) {
        List<OriginalThunderbolt> originalThunderbolts = loadDataFromCSV(csvList);

        List<Point> points = new ArrayList<>();
        List<OriginalThunderbolt> dmzThunderboltList = new ArrayList<>();
        originalThunderbolts.forEach(th -> {
            //雷点在区域内部
            if (SpatialUtils.isInPolygon(th.getLongitude(), th.getLatitude(), findParasFromResource())) {
                Point point = new Point(String.valueOf(th.getLongitude()), String.valueOf(th.getLatitude()));
                dmzThunderboltList.add(th);
                points.add(point);
            }
        });
        //传入sim服务,进行雷点与林场进行匹配
        List<OrganizationVO> matchedData = simOrgClient.matchAlarmPointWithOrgRegion(points);
        List<Thunderbolt> list = new ArrayList<>();
        //模型转换,插入数据库
        for (int i = 0; i < dmzThunderboltList.size(); i++) {
            OrganizationVO organizationVO = matchedData.get(i);
            OriginalThunderbolt originalThunderbolt = dmzThunderboltList.get(i);
            Thunderbolt thunderbolt = new Thunderbolt(organizationVO, originalThunderbolt);
            list.add(thunderbolt);

        }
        insertBatchThunderbolt(list);

    }

V4代码

 @Override
    public void transformCSV(Path filePath) {
        List<SynOrganizationVO> organizationVOList = simOrgClient.findAllByType("林场");
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath.toFile())));
            CSVParser parser = CSVFormat.DEFAULT.parse(reader);
            Iterator<CSVRecord> iterator = parser.iterator();
            //跳过表头
            if (iterator.hasNext()) {
                iterator.next();
            }

            List<List<String>> csvList = new ArrayList<>();
            while (iterator.hasNext()) {
                CSVRecord record = iterator.next();
                //每行的内容
                List<String> value = new ArrayList<>();
                for (int j = 0; j < record.size(); j++) {
                    value.add(record.get(j));
                }
                if (csvList.size() >= BATCH_SIZE) {
                    thunderBoltDataFilter(csvList,organizationVOList);
                    csvList.clear();
                }
                csvList.add(value);
            }
            thunderBoltDataFilter(csvList,organizationVOList);

        } catch (FileNotFoundException e) {
            log.error("临时文件未找到", e);
        } catch (Exception e) {
            log.error("未定义异常", e);
            deleteByStatus("未同步");
        }

    }

    private void flushDb() {
        remove(new QueryWrapper<>());
    }

    private void deleteByStatus(String status) {
        QueryWrapper<Thunderbolt> wrapper = new QueryWrapper<>();
        if (StringUtils.isNotEmpty(status)) {
            wrapper.eq("status", status);
        }
        remove(wrapper);
    }

    private List<OriginalThunderbolt> loadDataFromCSV(List<List<String>> csvList) {


        List<OriginalThunderbolt> originalThunderboltList = new ArrayList<>();

        for (List<String> csvString : csvList) {
            OriginalThunderbolt originalThunderbolt = new OriginalThunderbolt();

            originalThunderbolt.setId(SnowFlakeIdGenerator.getInstance().nextId());

            originalThunderbolt.setCode(csvString.get(0));

            if (StringUtils.isNotEmpty(csvString.get(1))) {
                Long time = TimeUtils.stringToDateLong(csvString.get(1));
                originalThunderbolt.setTime(time);
            }

            originalThunderbolt.setType(csvString.get(2));
            originalThunderbolt.setHeight(Double.valueOf(csvString.get(3)));
            originalThunderbolt.setStrength(Double.valueOf(csvString.get(4)));
            originalThunderbolt.setLatitude(Double.valueOf(csvString.get(5)));
            originalThunderbolt.setLongitude(Double.valueOf(csvString.get(6)));
            originalThunderbolt.setProvinces(csvString.get(7));
            originalThunderbolt.setCities(csvString.get(8));
            originalThunderbolt.setCounties(csvString.get(9));
            originalThunderbolt.setLocationMode(csvString.get(10));
            originalThunderbolt.setSteepness(csvString.get(11));
            originalThunderbolt.setDeviation(csvString.get(12));
            originalThunderbolt.setLocatorNumber(csvString.get(13));

            originalThunderbolt.setStatus("未同步");
            originalThunderbolt.setCreateTime(System.currentTimeMillis());
            originalThunderbolt.setUpdateTime(System.currentTimeMillis());
            originalThunderbolt.setDeleted(false);

            originalThunderboltList.add(originalThunderbolt);

        }

        return originalThunderboltList;
    }


测试结果

再没有对数据库进行任何改进的情况下,

20k数据量
v3,42s
v4,41s

更加糟糕的情况是在V4版本的时间并不稳定,有时候能达到近2分钟,而且有着数据越多时间并非线性增长的现象。反观V3时间则始终保持线性增长。

看到这测试结果,真是啪啪的打脸,赶紧再分析一遍问题。发现前面的推论有几个问题:

  1. 假设由点找面和由面找点的SQL执行时间相差不大。其实这一点并不成立,V3版的每次匹配的查询空间只有R个。而V4的查询则并不是是一个常数,查询空间随着批次的增加(i-1)*B。如果为数据增加状态并在批量处理前后调整状态,同时在数据库层面为状态加上索引应该可以在一定的程度上减轻这个问题。
  2. 在单个批次数据中V4的空间匹配时间要远远大大V3匹配时间,这个原因恐怕是底层的空间分析算法不同造成的,这个算法在某种程度上是一个黑盒。之前没有考虑过这个问题。

针对测试结果,果断把V4的修改回滚。。。

结论

  • 任何优化都不应该只停留在理论上,一定要动手尝试,在实际数据上测试;
  • 测试要建立在测试基准下,切忌凭感觉优化;
  • 做好代码的分支和保护,你的优化很可能要回滚。PS IDEA的localhistory功能真香;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 嵌入式Linux内存使用和性能优化是面向嵌入式系统设计人员和开发人员的重要课题。在使用嵌入式Linux系统时,合理地管理和优化内存使用对于系统的性能和稳定性至关重要。 首先,为了有效利用内存资源,我们需要了解嵌入式Linux系统的内存组成和内存管理机制。在嵌入式Linux系统,内存主要分为用户空间和内核空间。用户空间是为应用程序和用户数据分配的,而内核空间则是为内核代码和数据分配的。在内存管理方面,嵌入式Linux系统通常采用虚拟内存管理机制,通过内存管理单元(Memory Management Unit,MMU)实现虚拟内存的映射和管理。 其次,为了优化内存使用和提高性能,我们可以采取以下几个方面的措施: 1. 精简内核:嵌入式Linux系统的内核可以根据具体需求进行裁剪,只保留必要的功能和模块,避免不必要的内存占用。 2. 内存分配策略:合理地选择内存分配策略,如采用伙伴系统或者slab分配器,可以有效地管理内存碎片,并提高内存的使用效率。 3. 优化内存访问:合理地设计数据结构和算法,减少内存访问次数,尽量利用cache和缓冲区的局部性原理,提高内存访问效率。 4. 内存监控和调优:通过工具和技术监控和分析系统的内存使用情况,找出内存使用过高的原因,并进行相应的优化和调整。 最后,嵌入式Linux系统内存使用和性能优化是一个复杂而庞大的问题,需要根据具体的应用场景和需求进行综合考虑和优化。我们可以通过阅读相关的文献和书籍,参考相关的优化经验案例,不断提高我们的水平和技术能力。 ### 回答2: 嵌入式Linux的内存使用和性能优化对于系统的稳定性和性能至关重要。以下是有关这两个方面的一些重要内容。 在嵌入式Linux系统,内存的使用需要精心管理。一个高效的内存管理方案可以确保系统的稳定性和性能。首先,我们需要合理规划内存分配,根据系统的需求和资源限制,确定合适的内存大小。这可以通过调整内核配置文件或使用动态内存分配库实现。 其次,需要避免内存泄漏和内存碎片问题。内存泄漏指的是程序在分配了内存后却没有释放,导致系统内存不断增加,最终导致系统崩溃。内存碎片是指在多次申请和释放内存的过程,会导致内存块变得不连续,从而降低内存的分配效率。为了解决这些问题,可以使用内存检测工具和内存分配算法进行优化。 此外,优化嵌入式Linux的性能也是至关重要的。首先,我们需要合理选择硬件平台和处理器,以满足系统的性能需求。其次,要对系统进行优化配置,如设置合理的断和调度策略,以提高系统的响应速度。还可以通过使用优化的算法和数据结构来提高系统的处理能力。 此外,芯片供应商和软件开发者还可以通过优化编译器和驱动程序来提高系统的性能。编译器的优化选项可以帮助生成更高效的代码,而合理配置和调优驱动程序可以提高硬件的利用率。 最后,需要进行性能测试和调试,以评估系统的性能和发现潜在问题。性能测试可以帮助确定系统的瓶颈和优化空间,而调试可以帮助找出系统性能下降的原因,进而进行相应的优化。 总而言之,在嵌入式Linux系统,合理管理内存使用和优化系统的性能是非常重要的。通过合理规划内存分配、避免内存泄漏和碎片问题,优化系统配置和算法,以及进行性能测试和调试,可以提高系统的稳定性和性能。 ### 回答3: 嵌入式Linux系统的内存使用和性能优化是嵌入式开发的重要问题之一。通过优化内存使用,可以提升系统的性能和响应速度,同时减少资源的占用和浪费。 首先,我们可以通过合理的内存管理策略来优化内存使用。这包括使用静态内存分配和动态内存分配的结合,根据系统的需求和特点来合理分配静态内存和堆内存的大小。另外,可以使用内存池技术来进行内存的重复利用,避免频繁的内存分配和释放。 其次,我们可以通过减少内存的占用来优化性能。一种方法是精简系统不必要的模块和驱动,只保留必需的功能和驱动程序,避免过多的内存占用。另外,可以对系统的进程和线程进行优化,减少内存的开销。比如,可以使用轻量级线程库来减少线程的内存占用,使用线程池来复用线程资源。 还有,我们可以通过优化内存访问和数据结构来提升系统性能。对于频繁访问的数据,可以使用缓存技术来提高读写速度。对于大量的数据,可以考虑使用压缩算法进行压缩存储,减少内存占用。此外,可以合理选择数据结构和算法,避免资源的浪费。 最后,我们还可以通过工具进行性能分析和优化。可以使用工具来监控系统的内存使用情况,找出内存占用过高的地方,并进行优化。同时,可以使用性能分析工具来找出程序性能瓶颈,并进行相应的优化。 总之,嵌入式Linux系统的内存使用和性能优化是一个复杂的问题,需要综合考虑各个方面的因素。通过合理的内存管理、减少内存占用、优化内存访问和数据结构、以及使用工具进行性能分析和优化,可以提升系统的性能和响应速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值