前言

前段时间一直在用自己写的遗传算法框架测试算法在优化力场参数的效果,但是跑起来效率很慢,因为适应度函数需要调用多次力场程序计算能量,但是还是比我预想中的慢我也没有及时对程序进行profiling和优化。直到放假前在github有个使用gaft做SVM参数优化的童鞋开了个issue中说道在gaft优化的过程中会大量调用适应度函数,这才使我在国庆放假期间对gaft进行了profiling找到程序瓶颈并针对性的优化。

本文就记录下自己gaft做profiling并优化的过程以及优化的效果。

正文

对GAFT进行性能分析(Profiling)

关于如何对Python程序进行性能分析生成分析报告并可视化分析报告,我在之前的一篇博客里《Python优化第一步: 性能分析实践》进行了详细的介绍,这里我就直接分析了。

为了能针对gaft中不同的函数进行分析,借助Python内置的cProfilepstats模块我写了个装饰器方便分析并生成不同的分析统计文件。

 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def do_profile(filename, sortby='tottime'):
    '''
    Constructor for function profiling decorator.
    '''
    def _do_profile(func):
        '''
        Function profiling decorator.
        '''
        @wraps(func)
        def profiled_func(*args, **kwargs):
            '''
            Decorated function.
            '''
            # Flag for doing profiling or not.
            DO_PROF = os.getenv('PROFILING')
            if DO_PROF:
                profile = cProfile.Profile()
                profile.enable()
                result = func(*args, **kwargs)
                profile.disable()
                ps = pstats.Stats(profile).sort_stats(sortby)
                ps.dump_stats(filename)
            else:
                result = func(*args, **kwargs)
            return result
        return profiled_func
    return _do_profile

对上面的带参数的装饰器我在这里稍微解释下,装饰器构造器do_profile的两个参数filenamesortby分别指定分析结果报告的文件名以及统计结果的排序方式。它会对需要进行性能分析的函数进行装饰,然后在函数运行完后在当前目录生成结果报告。例如我需要对gaft中遗传算法迭代主循环进行分析,则需要:

 
 
 
 
 

Python

 
1
2
3
@do_profile(filename='gaft_run.prof')
def run(self, ng=100):
    ...

同时为了方便,我还需要一个环境变量PROFILING来启动分析:

 
 
 
 
 

Shell

 
1
export PROFILING=y

分析结果

这里为了方便查看函数的相互调用关系,我是用了pyprof2calltree 然后使用Mac上的QCacheGrind来可视化分析结果:

 
 
 
 
 

Shell

 
1
pyprof2calltree -i gaft_run.prof -k

将Python的profiling文件转换并直接调用QCacheGrind便可以方便的查看分析相关信息。

通过调用关系图可以看到,gaft的初始版本的min,max,mean等函数多次调用best_indvworst_indv会多次调用适应度函数来相互比较,而通常情况下用户自定义的适应度函数都是需要额外去调用外部程序的,一般都比较费时。所以必须要通过优化best_indvworst_indvfitness的调用次数才能提升gaft的效率。

优化GAFT

函数返回值缓存

从之前我写的best_indv中可以看到,我将fitness作为key用于获取最大值,Python内置的max函数会内部调用fitness进行相互比较来获取最大值,这个时候便对fitness进行了多余的调用,因为在遗传算法中,每一代的population中的个体是不会发生变化的我们只需要在每一次迭代的一开始调用fitnessn次就好了(n为种群大小),每一代中再次需要用到适应度值的地方直接获取。这样需要我们对种群中的个体进行惰性求值,也就是对所有的fitness的值进行缓存。这种操作我在优化自己的催化动力学程序的时候也使用过,叫做函数返回值缓存.

但是在gaft中这种缓存有稍微麻烦一点,因为缓存并不是缓存一次就可以一直用了,它会随着条件的变化需要重新计算种群中所有个体的适应度然后重新缓存。

重新计算适应度值需要同时满足的条件

  1. 种群中的所有个体没有发生任何变化 (如果变化了那肯定要重新计算适应度值了)。
  2. 已有缓存的适应度值 (如果是第一次那肯定需要计算一次所有个体的适应度值)。
  3. 计算适应度值的适应度函数与之前比较没有发生变化(如果计算适应度函数都改变了,那当然需要重新估计适应度值了)。

函数返回值缓存描述符

叉车问答

为此我写了个装饰器来缓存函数的返回值:

 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Memoized(object):
    '''
    Descriptor for population statistical varibles caching.
    '''
    def __init__(self, func):
        self.func = func
        self.result = None
        self.fitness = None
    def __get__(self, instance, cls):
        self.instance = instance
        return self
    def __call__(self, fitness):
        if ((not self.instance._updated)         # population not changed
                and (self.result is not None)    # result already cached
                and (fitness == self.fitness)):  # fitness not changed
            # Return cached result directly.
            return self.result
        else:
            # Update fitness function.
            self.fitness = fitness
            # Update and memoize result.
            self.result = self.func(self.instance, fitness)
            # Recover flag.
            self.instance._updated = False
            return self.result

动态监视种群的变化

好了上面我们可以通过描述符来缓存函数返回值,但是一旦种群不满足上述的三个条件就需要重新计算适应度值,那我们如何监控种群的变化呢?

我在GAPopulation中添加了一个标记_updated用于标记种群是否已经发生了变化, 然后我们的任务就是在其他能够影响到种群的地方试图去更新这个flag。

如何能更Pythonic的更新这个标记呢?

所谓的种群发生变化,也是就种群中的个体列表发生了变化,种群中的个体我都放在了一个列表中,我需要监控这个列表是否发生变化以便更新flag,具体又是那些变化呢?

  1. 列表整体是否发生了变化(赋值操作)
  2. 列表中的元素是否发生变化(对列表中的元素赋值操作,列表的appendextend操作等)

好了我们要具体怎么实现呢?

1)对于第一种,由于Python中无法进行赋值运算符重载,但是我们可以通过描述符的__set__来处理:

 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
class GAIndividuals(object):
    '''
    Descriptor for all individuals in population.
    '''
    def __init__(self, name):
        self.name = '_{}'.format(name)
    def __get__(self, instance, owner):
        return instance.__dict__[self.name]
    def __set__(self, instance, value):
        instance.__dict__[self.name] = value
        # Update flag.
        instance._updated = True

2)对于第二种情况,我们需要对Python的List类型的相应方法进行override

但是嘞,即使重写了list的接口,又如何更新population中的变量呢?这个时候就需要用闭包了。在GAPopulation的构造函数__init__中定义list的派生类,并立即实例化,这时候派生类的便可以获取population对象了,于是GAPopulation的构造函数可以这些写:

 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __init__(self, indv_template, size=100):
    # ...
    # Flag for monitoring changes of population.
    self._updated = False
    # Container for all individuals.
    class IndvList(list):
        '''
        A proxy class inherited from built-in list to contain all
        individuals which can update the population._updated flag
        automatically when its content is changed.
        '''
        # NOTE: Use 'this' here to avoid name conflict.
        def __init__(this, *args):
            super(this.__class__, this).__init__(*args)
        def __setitem__(this, key, value):
            '''
            Override __setitem__ in built-in list type.
            '''
            old_value = this[key]
            if old_value == value:
                return
            super(this.__class__, self).__setitem__(key, value)
            # Update population flag.
            self._updated = True
        def append(this, item):
            '''
            Override append method of built-in list type.
            '''
            super(this.__class__, this).append(item)
            # Update population flag.
            self._updated = True
        def extend(this, iterable_item):
            if not iterable_item:
                return
            super(this.__class__, this).extend(iterable_item)
            # Update population flag.
            self._updated = True
    self._individuals = IndvList()

优化效果

通过上面对代码的优化,我们看看我们优化的效果如何,使用分析描述符来分析GAEngine.run跑一代种群的情况,其中种群大小为10。如下图为cProfile生成的分析报告对比:

可以看到优化后的跑一代种群的时间缩短为将近原来的1/7! 优化效果还是很明显的。

然后看一看调用关系图:

energy_fitness的调用次数从3807降到了621次!

总结

本文记录了遗传算法框架GAFT的一次profiling和优化过程,通过缓存值的方式极大的减少了适值函数的调用次数,在时间上,跑一代种群的效率提升了7倍左右。

遗传算法框架GAFT优化小记的更多相关文章

  1. 遗传算法框架-基于java jenetics库实现

    本篇并非介绍如何从0开始开发遗传算法框架,反而推荐各位使用已有的GA库jenetics来做遗传算法. GA算法的逻辑还是贴下: 好了,下面介绍的是基于jenetics开发的更贴近业务侧的框架,以及使用 ...

  2. CLion之C++框架篇-优化开源框架,引入curl,实现get方式获取资源(四)

      背景   结合上一篇CLion之C++框架篇-优化框架,引入boost(三),继续进行框架优化!在项目中,我们经常会通过get方式拉取第三方资源,这一版优化引入类库curl,用来拉取第三方资源库. ...

  3. CLion之C++框架篇-优化框架,引入boost(三)

      背景   结合上一篇CLion之C++框架篇-优化框架,单元测试(二),继续进行框架优化!这一版优化引入一个我们日常经常使用的操作库Boost,估算使用频率在70%以上!   Boost的优势在哪 ...

  4. 大并发下TCP内存消耗优化小记(86万并发业务正常服务)

    转自:http://blog.csdn.net/u010954257/article/details/54178160 最近在做一个大并发服务的测试(目前测到86万,当然有大量长连接,每天打的日志高到 ...

  5. PHP程序Laravel框架的优化技巧

    Laravel是一套简洁.优雅的php Web开发框架(PHP Web Framework).它可以让你从杂乱的代码中解脱出来,可以帮你构建一个完美的网络app,而且每行代码都简洁.富于表达力.而性能 ...

  6. Laravel 5 框架性能优化技巧

    性能一直是 Laravel 框架为人诟病的一个点,所以调优 Laravel 程序算是一个必学的技能. 接下来分享一些开发的最佳实践,还有调优技巧,大家有别的建议也欢迎留言讨论 1.配置缓存信息 使用l ...

  7. CLion之C++框架篇-优化框架,单元测试(二)

    背景   结合上一篇CLion之C++框架篇-安装工具,基础框架的搭建(一),继续进行框架优化!   googletest(GTest)是Google开源的C++测试框架,与CLion组合,对C++环 ...

  8. 配置 FIS 来适配 go revel 框架以优化前端缓存策略

    对于前端工程师来说,浏览器缓存优化是个永远的话题.前几天看了知乎上的一个问答:<大公司里怎样开发和部署前端代码?>,深以为然,所以决心使用 FIS 来优化自身的前端文件. 我们的项目使用了 ...

  9. Jenkins Kubernetes Slave 调度效率优化小记

    Jenkins K8S Slave 调度效率优化 by yue994488@126.com 使用kubernetes为测试工具Gatling进行大规模压测,压测期间发现Jenkins调度压测实例较慢, ...

随机推荐

  1. centos7 安装mysql5.7以及一些细节问题

    突然发现我的新服务器上没有mysql,所以想安装一个,上次在我的window电脑上安装MySQL8.0我真的要气死了,和5.7修改密码的方式不一样,弄了很久,所以我决定还是不用安装8.0了,5.7就可 ...

  2. CentOS 7 Minimal 安装JDK 1.8

    真好最近比较闲,打算在linux 的CentOS 7 Minimal版本试着搭建hadoop环境学习学习,当然第一步就是在CentOS 7 Minimal 安装JDK 1.8环境.其实老早就打算了解一 ...

  3. case when 多个条件 以及case when 权重排序

    1. case when 多个条件 语法: SELECT nickname,user_name,CASE WHEN user_rank = '5' THEN '经销商' WHEN user_rank ...

  4. 需求:加一个下拉框选择条件改变饼图内外环 饼图:百度echarts提供

    1.1:下拉框条件:后台取得ViewBag传给前台 MonitorController: public ActionResult BigData(): //下拉框筛选条件 var result = M ...

  5. 解决Stm32出现error: #20: identifier "GPIO_InitTypeDef" is undefined异常

    该错误是我在移植sd卡程序时出现的,错误如下: error:#20,查看错误,可以发现,这些变量都是系统定义过的,没有修改过.并且该变量也能成功跳转被找到.那么到底是什么原因呢?逛了一些帖子,尝试了好 ...

  6. 物联网通信 - RESTDemo示例程序(C#版本)

    技术:wcf+http post+json(.net4.0 + jdk1.8) 运行环境:vs2010+java 概述Server开放RESTful API接口,供应用程序/移动App/嵌入式qt通过 ...

  7. 文件句柄W模式

    f1=open('lo',encoding='utf-8',mode='w')f1.write('w4567')print(f1.tell())f1.close() #tell 告诉指针的位置(按字节 ...

  8. 20155211 实验三 敏捷开发与XP实践

    20155211 实验三 敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 实验要求 完成实验.撰写实验报告,实验报告以博客方式发表在博客园. 实验步骤 (一)敏捷开发与XP 敏捷开发( ...

  9. 《Java 程序设计》课堂实践项目-命令行参数

    <Java 程序设计>课堂实践项目 课后学习总结 目录 改变 命令行参数实验要求 课堂实践成果 课后思考 改变 修改了博客整体布局,过去就贴个代码贴个图很草率,这次布局和内容都有修改.加了 ...

  10. Luogu1801_黑匣子_KEY

    题目传送门 借这道题练一下Treap和Splay的板子. code: #include <cstdio> #include <cstdlib> using namespace ...