精华内容
下载资源
问答
  • 点击蓝字 关注「前端小苑」精品技术文章,热门资讯第一时间送达一、设计模式综述我想很多和我一样朋友小时候都看过《天龙八部》,里面女主角王语嫣是个武学博才,但自己却毫无实战。比如段誉和慕...

    点击蓝字 关注「前端小苑」

    精品技术文章,热门资讯第一时间送达

    一、设计模式综述

    我想很多和我一样的朋友小时候都看过《天龙八部》,里面的女主角王语嫣是个武学博才,但自己却毫无实战。比如段誉和慕容复交手时,她连连口述指导:"段郎,二龙爪手,抢珠三式,当心你的腰肋,注意你的气户穴。潘月偷心,扶手相望......",虽然看着感觉都是一些最基本的拳脚功夫,但有解说在旁边,到底还是感觉高大上了很多。没错,设计模式其实就和这些招数名差不多,很多模式都给人一种其实平时没少用,可就是不知道原来这是一个专业招术...。但我们确实需要从系统层面深入理解一下这些常用的模式,不仅可以起到发散思维的作用,同时也可以指导我们解决问题的能力。如果之前很少接触过设计模式,那么这篇文章希望可以助力你一下,感谢关注和点赞。

    1.1 模式定义

    设计模式定义如下:

    在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。

    说白了,设计模式就是一种理念,通过一些设计思维来解决平时编写底层或业务代码时遇到的场景问题。比如早期业务中的一个封装类,同时带有一些封装方法。如果现在该类不能再满足全部业务场景,且不允许修改原方法,此时就需要装饰器或适配器模式来解决;又比如当设计一个场景,在调用一个固定对象时一定要先执行某些方法,比如验证登录、验证身份ID等场景,此时就应该用到代理模式。这种例子有很多,可以先看一下设计模式的分类。

    1.2 模式分类

    设计模式,按标准划分,有3大类23种,而由于JavaScript的一些特性,如弱类型语言、无接口编程等特征,故其中只有一些模式是比较重要的。下面给出这23种设计模式名称。

    是不是觉得这些高逼格的词汇很霸气,下面就先从一些重要的模式开展了解和深入。

    二、工厂模式

    1.1 基本特征

    工厂模式有三种形式:简单工厂模式(Simple Factory)、工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory)。在js中我们最常见的当属简单工厂模式。工厂模式的设计思想即:

    • 将 new 操作单独封装,只对外提供相应接口;

    • 遇到new 时,就要考虑是否应该使用工厂模式;

    1.2 核心作用

    工厂模式的核心作用如下:

    • 主要用于隐藏创建实例的复杂度,只需对外提供一个接口;

    • 实现构造函数和创建者的分离,满足开放封闭的原则;

    1.3 分类

    • 简单工厂模式:一个工厂对象创建一种产品对象实例。即用来创建同一类对象;

    • 工厂方法模式:建立抽象核心类,将创建实例的实际重心放在核心抽象大类的子类中;

    • 抽象工厂模式:对类的工厂抽象用来创建产品类簇,不负责创建某一类产品的实例。

    由于在JS中基本不会使用抽象工厂模式,因此本文探究前两类模式。

    1.4 实例演示

    先通过一个简单例子最直观感受什么是工厂:

    // 定义产品
    class Product {
        constructor(name) {
            this.name = name;
        }
        init() {
            console.log('初始化产品')
        }
    }
    
    // 定义工厂
    class Factory {
        create(name) {
            return new Product(name); // 核心思想
        }
    }
    
    let c = new Factory(); 
    let p = c.create('p1');
    p.init();
    

    工厂模式最直观的地方在于,创建产品对象不是通过直接new 产品类实现,而是通过工厂方法实现。现在再用一个稍微有些好看的例子描述一下简单工厂:

    //User类
    class User {
      //构造器
      constructor(opt) {
        this.name = opt.name;
        this.viewPage = opt.viewPage;
      }
    
      static getInstance(role) {
        switch (role) {
          case 'superAdmin':
            return new User({ name: '超级管理员', viewPage: ['首页', '通讯录', '发现页', '应用数据', '权限管理'] });
            break;
          case 'admin':
            return new User({ name: '管理员', viewPage: ['首页', '通讯录'] });
            break;
          default:
            throw new Error('params error')
        }
      }
    }
    
    //调用
    let superAdmin = User.getInstance('superAdmin');
    let admin = User.getInstance('admin');
    

    复制代码通过上例,我们可以看到,每次创建新的对象实例时,只需要传入相应的参数,就可以得到指定的对象实例。最直观的例子是如果不用工厂模式,那代码中是不是就会多出好多个new,这样看着也不太舒服。

    其实简单工厂模式已经能满足我们前端大部分业务场景了,如果非要说其一个缺陷,那就是每次有新实例时,我们需要重写这个User大类,总归感觉和后面所述的装饰器模式有一些冲突。此时,工厂方法模式就出来了,其核心思想就是独立出一个大的User类,将创建实例对象的过程用其子类来实现:

    class User {
      constructor(name = '', viewPage = []) {
        this.name = name;
        this.viewPage = viewPage;
      }
    }
    
    class UserFactory extends User {
      constructor(name, viewPage) {
        super(name, viewPage)
      }
      create(role) {
        switch (role) {
          case 'superAdmin': 
            return new UserFactory( '超级管理员', ['首页', '通讯录', '发现页', '应用数据', '权限管理'] );
            break;
          case 'admin':
            return new UserFactory( '管理员', ['首页', '通讯录'] );
            break;
          default:
            throw new Error('params error');
        }
      }
    }
    let userFactory = new UserFactory();
    let superAdmin = userFactory.create('superAdmin');
    let admin = userFactory.create('admin');
    let user = userFactory.create('user');
    

    这样,虽然也得通过 new 一个实例,但至少我们可以无需修改User类里面的东西,虽说代码量上感觉和简单模式差不了多少,但思想主体确实就是这样。

    1.5 应用场景

    (1) jQuery的选择器$(selector)

    $('div')new $('div') 有何区别?为什么 $('div') 就能直接实现 new 的效果,同时去除了 new $('div') 这种 $('div') 去除了 new 书写繁杂的弊端,还能实现完美的链式操作代码简介,就是因为 $ 内置的实现机制是工厂模式。其底层代码如下:

    class jQuery {
        constructor(selector) {
            super(selector)
        }
        // ...
    }
    
    window.$ = function(selector) {
        return new jQuery(selector)
    }
    

    (2) Vue 异步组件

    Vue.component('async-example' , (resolve , reject) => {
        setTimeout(function() {
            resolve({
                template: `<div>I am async!</div>`
            })
        }, 1000)
    })
    

    除了上述两个常见的实例场景,还有 React.createElement() 也是工厂原理。所以,当我们平时遇到要创建实例的时候,就可以想想能否用工厂模式实现了。

    三、单例模式

    3.1 基本特征

    单例模式,顾名思义即保证实例在全局的单一性,概述如下:

    • 系统中被唯一使用

    • 一个类只有一个实例(注意只能有一个实例,必须是强相等===)

    在日常业务场景中,我们经常会遇到需要单例模式的场景,比如最基本的弹窗,或是购物车等。因为不论是在单页面还是多页面应用程序中,我们都需要这些业务场景只会同时存在一个。而如果用单例模式,则会避免需要外部变量来判定是否存在的低端方法。

    3.2 实例演示

    举一个单例模式的例子:

    class Modal {
        login() {
            console.log('login...');
        }
    }
    Modal.create = (function() {
        let instance
        return function() {
            if(!instance) {
               instance = new Modal();
            }
            return instance
        }
    })()
    let m1 = Modal.create();
    let m2 = Modal.create();
    console.log(m1 === m2) // true
    

    上述代码是一种简单版单例模式,通过js的立即执行函数和闭包函数,将初始实例确定,之后便可通过判定 instance 是否存在,果存在则直接返回,反之则创建了再返回,即确保一个类只有一个实例对象。还有一种种“透明版”单例模式:

    let Modal = (function(){
        let instance;
        return function(name) {
            if (instance) {
               return instance;
            }
            this.name = name;
            return instance = this;
        }
    })();
    
    Modal.prototype.getName = function() {
        return this.name
    }
    
    let question = new Modal('问题框');
    let answer = new Modal('回答框');
    
    console.log(question === answer); // true
    console.log(question.getName());  // '问题框'
    console.log(answer.getName());  // '问题框'
    

    所以,单例模式的实现实质即创建一个可以返回对象实例的引用和一个获取该实例的方法。保证创建对象的引用恒唯一。

    3.3 应用场景

    单例模式应用场景太多了,在Vue 中 我们熟知的Vuex 和 redux 中的 store。

    四、适配器模式

    4.1 定义及特征

    适配器模式很好理解,在日常开发中其实不经意间就用到了。适配器模式(Adapter)是将一个类(对象)的接口(方法或属性)转化成适应当前场景的另一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。所以,适配器模式必须包含目标(Target)、源(Adaptee)和适配器(Adapter)三个角色。

    4.2 应用场景

    举个我工作中最生动简单的例子,你就知道原来适配器无处不在。前端通过接口请求来一组数据集,类型分别文章、回答和课程,其中文章类返回的日期类型是 2019-08-15 09:00:00 格式字符串,回答类是 2019/08/15 09:00:00 ,课程类返回的是时间戳格式,且文章、回答的创建时间字段叫createAt,课程叫createTime(我们真就是这样......)返回数据如下:

    let result = [
          {
              id: 1
              type: 'Article',
              createAt: '2019-06-12 08:10:20',
              updateAt: '2019-08-15 09:00:00',
              ......
          },
          {
              id: 2
              type: 'Answer',
              createAt: '2019-04-11 08:11:23',
              updateAt: '2019/08/15 09:00:00',
              ......
          },
          {
              id: 3
              type: 'Course',
              createTime: 1554941483000,
              updateAt: 1565830800000,
              ......
          }
        ]
    

    现在我们要呈现这些实体的格式到移动端。并显示一个统一的时间格式。而一般情况下在遇到时间类型时,我们通常首先想到的就是先 new Date() 一下,再做相应的转换,但是很遗憾,在移动端IOS系统上,2019-08-15这种横杠分隔格式的时间是不被识别的,所以,我们此时就需要做个数据适配器做兼容处理:

     let endResult = result.map(item => adapter(item));
     
     let adapter = function(item) {
        switch(item.type) {
            case 'Article':
              [item.createAt, item.updateAt] = [
                 new Date(item.createAt.replace(/-/g,'/')).getTime(),
                 new Date(item.updateAt.replace(/-/g,'/')).getTime()
              ]
            break;
            case: 'Answer': 
              item.createAt = new Date(item.createAt.replace(/-/g,'/')).getTime();
            break;
            case: 'Course':
              item.createAt = item.createTime
            break;
        }
     }
    

    恩,没错,这个adapter 也可以叫做数据适配器,有了这个方法,所有实体数据类型的数据就都可适配了。

    再看一个基于ES6类的适配器例子:

    // 目标
    class Target {
        typeGB() {
            throw new Error('This method must be overwritten!');
        }
    }
    
    // 源
    class Adaptee {
        typeHKB() {
            console.log("香港(Hong Kong)标准配件"); // 港独都是sb
        }
    }
    
    // 适配器
    class Adapter extends Target {
        constructor(adaptee) {
            super();
            this.adaptee = adaptee;
        }
        typeGB() {
            this.adaptee.typeHKB();
        }
    }
    
    let adaptee = new Adaptee();
    
    let adapter = new Adapter(adaptee);
    adapter.typeGB(); //香港(Hong Kong)标准配件
    

    上述实例就将 Adaptee 类的实例对象的 typeHKB() 适配了通用的 typeGB() 方法。另外我不想重申官方说过的话,我只想直白一些:港..独都是sb

    五、装饰器模式

    5.1 定义及特征

    装饰器,顾名思义,就是在原来方法的基础上去装饰一些针对特别场景所适用的方法,即添加一些新功能。因此其特征主要有两点:

    • 为对象添加新功能;

    • 不改变其原有的结构和功能,即原有功能还继续会用,且场景不会改变。

    直接上个例子:

    class Circle {
        draw() {
            console.log('画一个圆形');
        }
    }
    
    class Decorator {
        constructor(circle) {
            this.circle = circle;
        }
        draw() {
            this.circle.draw();
            this.setRedBorder(circle);
        }
        setRedBorder(circle) {
            console.log('画一个红色边框');
        }
    }
    
    let circle = new Circle();
    let decorator = new Decorator(circle);
    decorator.draw(); //画一个圆形,画一个红色边框
    

    该例中,我们写了一个 Decorator 装饰器类,它重写了实例对象的 draw 方法,给其方法新增了一个 setRedBorder() ,因此最后为其输出结果进行了装饰。

    5.2 装饰器插件

    ES7 中就存在了装饰器语法,需要安装相应的babel插件,一起看一下该插件如何用,首先安装一下插件,并做相关的语法配置:

    npm i babel-plugin-transform-decorators-legacy 
    
    //.babelrc
    {
        "presets": ["es2015", "latest"],
        "plugins": ["transform-decorators-legacy"]
    }
    

    给一个Demo类上添加一个装饰器 testDec,此时 Demo类就具有了 装饰器赋予的属性:

    @testDec
    class Demo {}
    
    function testDec(target) {
       target.isDec = true;
    }
    
    alert(Demo.isDec) // true
    

    通过上例可以得出下述代码结论:

    @decorator 
    class A {}
    
    // 等同于
    
    class A {}
    A = decorator(A) || A;
    

    5.3 实力场景

    装饰器的实例场景有很多,我们主要拿mixin和属性装饰学习一下。

    (1) mixin 示例

    function mixins(...list) {
       return function(target) {
          Object.assign(target.prototype, ...list)
       }
    }
    
    const Foo = {
        foo() {
            alert('foo');
        }
    }
    
    @mixins(Foo)
    class MyClass { }
    
    let obj = new MyClass();
    obj.foo();
    

    上例中,Foo 作为 target 的实参,MyClass 作为 list 的实参,最终实现将 Foo 的所有原型方法(foo)装饰到 MyClass 类上,成为了 MyClass 的方法。最终代码的运行结果是执行了 foo()

    (2) 属性装饰器

    固定语法:

    function readonly(target, name, descriptor) {
        // descriptor 属性描述对象(Object.defineProperty 中会用到)
        /*
          {
              value: specifiedFunction,
              enumerable: false,
              configurable: true
              writable: true 是否可改
          }
        */
    }
    

    设置类属性只读:

    function readonly(target , name , descriptor) {
      descriptor.writable = false;
    }
    
    class Person {
        constructor() {
            this.first = '周';
            this.last = '杰伦';
        }
    
        @readonly
        name() {
            return `${this.first}${this.last}`
        }
    }
    
    const p = new Person();
    console.log(p.name());  // 打印成功 ,‘周杰伦’
    
    // 试图修改name:
    p.name = function() {
        return true;
    }
    // Uncaught TypeError:Cannot assign to read only property 'name' of object '#<Person>'
    

    可见,再给属性添加了只读的装饰后,代码试图修改属性的命令将会报错。

    六、代理模式

    6.1 定义及特征

    代理模式的定义如下:

    为一个对象提供一个代用品或占位符,以便控制对它的访问。

    通俗来说,代理模式要突出“代理”的含义,该模式场景需要三类角色,分别为使用者、目标对象和代理者,使用者的目的是直接访问目标对象,但却不能直接访问,而是要先通过代理者。因此该模式非常像明星代理人的场景。其特征为:

    • 使用者无权访问目标对象;

    • 中间加代理,通过代理做授权和控制。

    代理模式确实很方便,通常如果面临一些很大开销的操作,就可以并采用虚拟代理的方式延迟到需要它的时候再去创建,比如懒加载操作。或者一些前置条件较多的操作,比如目标操作实现的前提必须是已登录,且Id符合一定特征,此时也可以将这些前置判断写到代理器中。举个加载图片的例子:

    class ReadImg {
        constructor(fileName) {
           this.fileName = fileName;
           this.loadFromDisk();
        }
    
        display() {
            console.log('display...' + this.fileName);
        }
    
        loadFromDisk() {
            console.log('loading...' + this.fileName);
        }
    }
    
    class ProxyImg {
        constructor(fileName) {
           this.readImg = new ReadImg(fileName)
        }
    
        display() {
            this.readImg.display();
        }
    }
    
    let proxyImg = new ProxyImg('1.png');
    proxyImg.display();
    

    6.2 实际应用

    (1) HTML元素事件代理:

    HTML元素代理事件,又名网页代理事件,举例如下:

    <body>
        <div id="div1">
            <a href="#">a1</a>
            <a href="#">a2</a>
            <a href="#">a3</a>
            <a href="#">a4</a>
            <a href="#">a5</a>
        </div>
    
        <script>
           var div1 = document.getElementById('div1');
           div1.addEventListener('click', (e) => {
              var target = e.target;
              if(target.nodeName === 'A') {
                 alert(target.innerHTML);
              }
           })
    </script>
    </body>
    

    该例中,我们并未直接在元素上定义点击事件,而是通过监听元素点击事件,并通过定位元素节点名称来代理到<a>标签的点击,最终利用捕获事件来实现相应的点击效果。

    (2) $.proxy

    $.proxy 是 jQuery 提供给我们的一个代理方法,还以上述 html 元素为例,写一个点击事件:

    // html如上例
    $('#div1').click(function() {
       setTimeout(function() {
          $(this).css('background-color', 'yellow')
       },1000)
    })
    

    上述div的点击最终不会实现背景色变化,因为 setTimeout 的因素,导致内部函数中的 this 指向的是 window 而非相应的 div通常我们的做法是在 setTimeout 方法前获取当前 this 指向,代码如下:

    $('#div1').click(function() {
       let _this = this;
       setTimeout(function() {
          $(_this).css('background-color', 'yellow')
       },1000)
    })
    

    而如果不用上面的方法,我们就可以用$.proxy代理目标元素来实现:

    $('#div1').click(function() {
        var fn = $.proxy(function() {
            $(this).css('background-color', 'yellow')
        }, this);
        
        setTimeout(fn , 1000)
    })
    

    (3) ES6 proxy

    ES6的 Proxy 相信大家都不会陌生,Vue 3.0 的双向绑定原理就是依赖 ES6 的 Proxy 来实现,给一个简单的例子:

    let star = {
        name: '菜徐坤',
        song: '~鸡你太美~'
        age: 40,
        phone: 13089898989
    }
    
    let agent = new Proxy(star , {
        get(target , key) {
            if(key == 'phone') {
                // 返回经济人自己的电话
                return 15667096303
            }
            if(key == 'price') {
               return 20000000000
            }
            return target[key]
        },
        set(target , key , val) {
           if(key === 'customPrice') {
              if(val < 100000000) {
                  throw new Error('价格太低')
              }
              else {
                  target[key] = value;
                  return true
              }
           }
        }
    })
    
    // agent 对象会根据相应的代理规则,执行相应的操作:
    agent.phone // 15667096303  
    agent.price // 20000000000 
    

    不用多解释了,真不明白他咋火的。

    七、观察者模式

    7.1 定义及特征

    观察者模式有多重要?这么说吧,如果上帝告诉你,这辈子你只能学习一种模式,你该毫不犹豫选择观察者模式。观察者模式,也叫订阅-发布模式,熟悉Vue的朋友一定不会陌生,该模式定义了一种1对N的关系(注意:不一定是一对多,所以更准确地描述应该是1对N),使观察者们同时监听某一个对象相应的状态变换,一旦变化则通知到所有观察者,从而触发观察者相应的事件。因此,观察者模式中的角色有两类:观察者(发布者)和被观察者(订阅者)。

    我们可直接看一下观察者模式的UML类图:

    类图解析:

    • 每一个观察者(Observer)都有一个update 方法,并且观察者的状态就是等待被触发;

    • 每一个主题(subject)都可以通过attach方法接纳N个观察者所观察,即观察者们存储在主题的observers数组里;

    • 主题有初始化状态(init)、获取状态(getState)和设置状态(setState)三个通用型方法;

    • 当主题的状态发生变化时,通过特定的notifyAllObervers方法通知所有观察者。

    这下就很明白了,针对如上描述再来个小例子:

    // 创建一个主题,保存状态,状态变化之后触发所有观察者对象
    class Subject {
        constructor() {
            this.state = 0;
            this.observers = []
        }
    
        getState() {
            return this.state
        }
    
        setState(state) {
           this.state = state;
           this.notifyAllObservers()
        }
    
        notifyAllObservers() {
            this.observers.forEach(observer => {
                observer.update()
            })
        }
    
        attach(observer) {
           this.observers.push(observer)
        }
    }
    
    // 观察者
    class Observer {
        constructor(name , subject) {
           this.name = name;
           this.subject = subject;
           this.subject.attach(this);
        }
        update() {
            console.log(`${this.name} update, state: ${this.subject.getState()}`)
        }
    }
    
    let s = new Subject();
    let o1 = new Observer('o1' , s);
    let o2 = new Observer('o2' , s);
    let o3 = new Observer('o3' , s);
    
    s.setState(1)
    s.setState(2)
    s.setState(3)
    
    /*
    o1 update, state: 1
     o2 update, state: 1
    o3 update, state: 1
    o1 update, state: 2
    o2 update, state: 2
    o3 update, state: 2
    o2 update, state: 3
    o3 update, state: 3
    */
    

    通过最终结果不能看到,主题每次改变状态后都会触发所有观察者状态更新,主题触发了3次状态,观察者一定update了9次。

    7.2 实例场景

    其实我们在平时不经意间就使用了很多观察者模式的例子,比如Promise等、Node.js中的 EventEmitter事件监听器、Vue 的 Watch生命周期钩子等等,这些都是观察者模式,比如在Vue组件生命周期Watch,为甚在Watch里设定了数据监听,一旦数据改变了就触发相应事件了?还有Promise,为什么异步操作得到结果后就会进入到then或者catch里呢?这些都依赖于观察者模式。这里我引用一篇很不错的文章《vue的双向绑定原理及实现》

    好了,这篇文章的内容就先告一段落,我们已经把23中设计模式中的核心重点都过了一遍,剩下的一些非重点,我会尽快整理出来,欢迎大家关注和点赞。

    感谢千阳老师的校验。

    参考文章

    《Javascript 设计模式系统讲解与应用》:https://coding.imooc.com/class/255.html

    《vue的双向绑定原理及实现》:https://www.cnblogs.com/canfoo/p/6891868.html

    《ES6实现之适配器模式Adapter》:https://juejin.im/post/5ad07d2b5188255570066f16

    《JavaScript设计模式与实践--工厂模式》:https://juejin.im/post/5b69c699e51d45348a301ef4


    关于本文

    作者:_呜啦啦啦火车笛

    来源:掘金

    原文链接:https://juejin.im/post/5d58ca046fb9a06ad0056cc7

    版权声明:版权归原作者所有


    喜欢本篇内容请给我们点个在看

    展开全文
  • 搜索引擎会将HTML中某些元素标签作为影响网页排名重要依据在我们之前文章中也或多或少地向大家介绍了有关HTML代码的优化技巧,接下来将系统地讲解HTML代码的几个优化重点。1.Title 标签Title 标签能够告诉用户...

    众所周知,HTML代码一直是搜索引擎抓取的重点。搜索引擎会将HTML中的某些元素标签作为影响网页排名的重要依据

    在我们之前的文章中也或多或少地向大家介绍了有关HTML代码的优化技巧,接下来将系统地讲解HTML代码的几个优化重点。


    1.Title 标签

    Title 标签能够告诉用户和搜索引擎网页页面的主题思想是什么,一直是搜索引擎的抓取重点。通常,搜索引擎抓取Title标签出于两个目的:作为影响网页排名的重要因素和作为搜索结果页面的显示信息。不管是哪一个目的,对我们做SEO来说都非常重要。
    一般来说,Title标签中的单词最好保持在3~6个左右,最好包含关键字。但Title标签中的单词不要全部都是关键字,因为这样可能会造成页面关键字堆砌,导致过度优化。所选单词应简洁明了、具有描述性,要与网页内容具有很大的相关性,并且每个不同的页面都应该包含Title标签。

    2.Meta Description标签

    对Title标签优化之后,接下来就是对Meta Description标签的优化。Meta Description标签可以说是对Title标签的进一步解释,可以是一句话或者是包含十几个单词的短语。每个页面都该有其自己的Meta Description标签,并且Meta Description标签还可包含一些与网站内容相关但Title标签中未提及的信息。与Title标签要求相似,该部分内容也应具有描述性,与网页内容具有相关性,可包含关键字,但不可过多。

    3.Heading标签

    Heading标签包含了H1、H2、H3等等,是搜索引擎识别页面信息的重要标记。合理使用H1、H2、H3等不同级别的标签能够使得页面结构更加清晰,有利于搜索引擎的抓取。H1、H2、H3等标签是按照重要程度来排名的。一般一个页面按照需求程度来适当添加该标签:从H1开始,依次往下添加。但不可添加太多Heading标签,否则会适得其反。

    4.Strong和B标签

    相信大多数朋友都知道Strong和B标签都有加粗的意思,但是很多人并不清楚两者具体有什么区别。其实B标签就是单纯地将文字加粗,而Strong标签不仅是对文字加粗,并且这种形式的加粗会告诉搜索引擎该部分文字比较重要。所以两者从搜索引擎优化的角度来看,是有很大的区别的。
    上文中所提到的Heading标签页具有加粗效果,那么这三种标签到底该怎么用?其实,Heading标签一般用于文章大标题以及每段的小标题,而Strong标签一般用于文章段落中的重点词汇,而B标签一般只是强调一种视觉效果。

    5.ALT标签

    ALT标签是一种图片标签,它将图片的信息以文本的形式展现。对ALT标签的使用没有太多要求,只要在网页中出现图片的部分添加上该属性即可,但其标签内容应与相应页面内容具有相关性,长度不得过长,一般1~5个单词即可。

    以上介绍了HTML代码中的五种重要标签,相信会对做SEO工作的人员特别是SEO新手具有很大帮助。对HTML代码的优化一直是我们做搜索引擎优化工作的非常基础并且重要的一部分,只有做好了这部分优化工作,我们才能开展更加深层的优化。


    原文:http://www.jb51.net/yunying/154286.html


    展开全文
  • Python代码优化概要

    2017-05-02 20:39:00
    虽是脚本语言,但相同涉及到代码优化的问题,代码优化可以让程序执行更快,它是在不改变程序执行结果情况下使程序执行效率更高,依据80/20原则。实现程序重构、优化、扩展以及文档相关事情通常须要消耗80%...

    Python即是面向过程语言,也是面向对象语言,很多其它情况下充当脚本语言的角色。虽是脚本语言,但相同涉及到代码优化的问题,代码优化可以让程序执行更快,它是在不改变程序执行结果的情况下使程序执行效率更高,依据80/20原则。实现程序的重构、优化、扩展以及文档相关的事情通常须要消耗80%的工作量。

    优化通常包括双方面的内容:

    1. 减小代码的体积、提高代码的可读性及可维护性

    2. 改进算法,减少代码复杂度,提高代码执行效率。

    选择合适的数据结构一个良好的算法可以对性能起到关键作用。因此性能改进的首要点是对算法的改进。

    在算法的时间复杂度排序上依次是:

    O(1) > O(lg n) > O(n lg n) > O(n^2) > O(n^3) > O(n^k) > O(k^n) > O(n!)


    比方说字典是哈希结构。遍历字典算法复杂度是O(1),而列表算法复杂度是O(n),因此查找对象字典比列表快。

    以下列出一些代码优化的技巧。以概要方式总结。因为时间关系。仅仅总结当中一部分。以后会持续更新。

    说明

    測试的工具: 包含time模块,timeit模块,profile模块或cProfile模块

    验证的方式包含Python ShelliPythonPython脚本

    測试的环境: 包含Python 2.7.6IPython 2.3.1 

    NOTE: 

    1. 一般来说c开头是c语言实现,速度更快些,比方cProfile就比profile快。

    cPickle比pickle快。

    2. 一般来说Python版本号较高。在速度上都有非常大提升。所以測试环境不同,结果不一样。

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    += 比 +快

    从Python2.0開始,添加了增强性数据类型,比方说

    X += Y

    等价于X = X + Y

    1. 就优化来说。左側仅仅需计算一次。在X += Y中,X能够使复杂的对象表达式。在增强形式中,则仅仅须要计算一次。

    然而,在完整的X = X + Y中,X出现两次,必须运行两次。因此增强赋值语句通常更快些。

    from timeit import Timer   #记得导入timeit模块

    In [4]: Timer('S = S + "eggs"','S = "SPAM"').timeit()
    Out[4]: 2.8523161220051065
    
    In [5]: Timer('S += "eggs"','S = "SPAM"').timeit()
    Out[5]: 2.602857082653941
    2. 优化技术会自己主动选择,对于支持原处改动的对象而言,增强形式会自己主动运行原处的改动。

    普通复制:

    >>> M = [1,2,3]
    >>> L = M
    >>> M = M + [5]
    >>> M;L
    [1, 2, 3, 4]
    [1, 2, 3]
    原处改动:
    >>> M  = [1,2,3]
    >>> L  = M
    >>> M += [4]
    >>> M;L
    [1, 2, 3, 4]
    [1, 2, 3, 4]
    
    >>> Timer('L = L + [4,5,6]','L = [1,2,3]').timeit(20000)
    4.324376213615835
    >>> Timer('L += [4,5,6]','L = [1,2,3]').timeit(20000)
    0.005897484107492801
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    可变对象内置函数比合并操作快

    第一种方法: 普通加入来实现

    >>> L = [1,2,3]
    >>> L = L + [4]
    >>> L
    [1, 2, 3, 4]
    另外一种方法: 内置函数来实现
    >>> L = [1,2,3]
    >>> L.append(4)
    >>> L
    [1, 2, 3, 4]
    其所花费的时间,相差数百倍:
    >>> Timer('L = L + [4]','L = [1,2,3]').timeit(50000)
    8.118179033256638
    >>> Timer('L.append(4)','L = [1,2,3]').timeit(50000)    #内置函数append()方法
    0.01078882192950914
    >>> Timer('L.extend([4])','L = [1,2,3]').timeit(50000)  #内置函数extend()方法
    0.020846637858539907
    普通的合并操作尽管没有共享引用带来的副作用,与等效的原处改动相比。但速度非常慢。合并操作必须建立新的对象,复制左側的列表,再复制右側的列表。与之相比的是:在原处的改动法仅仅会在内存块的末尾加入元素。

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    布尔測试比边界測试快

    >>> Timer('X < Y and Y < Z','X=1;Y=2;Z=3').timeit(100000000)  #布尔測试
    7.142944090197389
    >>> Timer('X < Y < Z','X=1;Y=2;Z=3').timeit(100000000)        #边界測试,推断Y结余X,Z之间
    11.501173499654769
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    短路运算比and运算快

    在Python中,if用于条件推断,有以下几种情况

    X and Y:  X与Y同一时候为真。方为真

    X  or Y:  X或Y任一位真,就为真。 也叫短路运算。即假设前面为真,后面则不推断

    not X:    X为假时方为真

    In [28]: Timer('2 or 3').timeit(100000000)  #短路运算:前面为真,后面不运算,所以速度快些
    Out[28]: 3.780060393088206
    
    In [29]: Timer('2 and 3').timeit(100000000) #and。必须运算为全部的,速度相对慢些
    Out[29]: 4.313562268420355
    
    In [30]: Timer('0 or 1').timeit(100000000)  #or运算,但前面为假。所以和前面速度相当
    Out[30]: 4.251177957004984
    
    In [31]: Timer('not 0').timeit(100000000)   #not运算,仅仅须要推断一个条件,速度快些
    Out[31]: 3.6270803685183637
    在前面三个表达式中,短路运算和not运算无疑速度快些,and运算和or中前面条件为假者速度慢些。

    所以在程序中适当使用,能够提高程序效率.

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    append比insert速度快

    列表的append方法要比insert方法快的多,由于兴许的引用必须被移动以便使新元素腾地方.

    复杂度append末尾加入,复杂度O(1)。而insert复杂度是O(n)

    >>> Timer('L.append(4)','L=[1,2,3,5,6]').timeit(200000)
    0.03233202260122425
    >>> Timer('L.insert(3,4)','L=[1,2,3,5,6]').timeit(200000)
    18.31223843438289
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    成员变量測试:字典和集合快于列表和元祖

    能够用in来做成员变量推断,比方'a' in 'abcd'

    推断列表和元祖中是否含有某个值的操作要比字典和集合慢的多。

    由于Python会对列表中的值进行线性扫描。而另外两个基于哈希表,能够瞬间完毕推断。

    数据越大,越明显。

    In [44]: Timer('4 in L','L=(1,2,3,4,5,6,7,8,9)').timeit(100000000)
    Out[44]: 12.941504527043435    #列表成员推断
    
    
    In [45]: Timer('4 in T','T=[1,2,3,4,5,6,7,8,9]').timeit(100000000)
    Out[45]: 12.883945908790338    #元祖成员推断,和列表几乎相同
    
    
    In [46]: Timer('4 in S','S=set([1,2,3,4,5,6,7,8,9])').timeit(100000000)
    Out[46]: 6.254324848690885     #集合成员推断。和字典几乎相同
    
    
    In [47]: Timer('4 in D','D={1:"a",2:"b",3:"c",4:"d",5:"e",6:"f",7:"g",8:"h",9:"i"}').timeit(100000000)
    Out[47]: 6.3508488422085065    #字典成员推断

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    列表合并extend比+速度快

    列表合并(+)是一种相当费资源的操作,由于必须创建一个新列表并将全部对象复制进去。

    而extend将元素附加到现有列表中,因此会快非常多,尤其是创建一个大列表时尤其如此.

    +操作运行结果:

    import profile            #用cProfile会快些
    
    def func_add():           #測试列表合并操作
        lst = []
        for i in range(5000): 
            for item in [[0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10]]:
                lst = lst + item
                
    if __name__=='__main__':
        profile.run('func_add()')
    #####測试结果:#####
    >>> 
             5 function calls in 9.243 seconds
    
       Ordered by: standard name
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.000    0.000    0.000    0.000 :0(range)
            1    0.006    0.006    0.006    0.006 :0(setprofile)
            1    0.000    0.000    9.237    9.237 <string>:1(<module>)
            1    9.236    9.236    9.236    9.236 Learn.py:3(func_add)
            1    0.000    0.000    9.243    9.243 profile:0(func_add())
            0    0.000             0.000          profile:0(profiler)

    extend运行结果:

    import profile
    
    def func_extend():
        lst = []
        for i in range(5000):
            for item in [[0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10]]:
                lst.extend(item)
        
    
    if __name__=='__main__':
        profile.run('func_extend()')
    
    #####输出结果:#####
    >>> 
             55005 function calls in 0.279 seconds
    
       Ordered by: standard name
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        55000    0.124    0.000    0.124    0.000 :0(extend)
            1    0.000    0.000    0.000    0.000 :0(range)
            1    0.005    0.005    0.005    0.005 :0(setprofile)
            1    0.000    0.000    0.274    0.274 <string>:1(<module>)
            1    0.149    0.149    0.273    0.273 Learn.py:3(func_extend)
            1    0.000    0.000    0.279    0.279 profile:0(func_extend())
            0    0.000             0.000          profile:0(profiler)

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    xrange比range快

    In [9]: Timer('for i in range(1000): pass').timeit()
    Out[9]: 30.839959527228757
    
    In [10]: Timer('for i in xrange(1000): pass').timeit()
    Out[10]: 19.644791055468943
    xrange是range的C语言实现。更高效的内存管理。

    xrange:每次仅仅迭代一个对象

    range:一次生成全部数据,须要一个个扫描

    NOTE: 在Python3.0中取消了xrange函数,仅仅留range。无论这个range事实上就是xrange,仅仅只是名字变了。

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    内置函数>列表推导>for循环>while循环

    http://blog.csdn.net/jerry_1126/article/details/41773277

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    局部变量>全局变量

    import profile
    
    A = 5
    
    def param_test():
        B = 5
        res = 0
        for i in range(100000000):
            res = B + i
        return res
            
    if __name__=='__main__':
        profile.run('param_test()')
    >>> ===================================== RESTART =====================================
    >>> 
             5 function calls in 37.012 seconds  #全局变量測试结果:37 s
    
    
       Ordered by: standard name
    
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1   19.586   19.586   19.586   19.586 :0(range)
            1    1.358    1.358    1.358    1.358 :0(setprofile)
            1    0.004    0.004   35.448   35.448 <string>:1(<module>)
            1   15.857   15.857   35.443   35.443 Learn.py:5(param_test)
            1    0.206    0.206   37.012   37.012 profile:0(param_test())
            0    0.000             0.000          profile:0(profiler)
    
    
    
    
    >>> ===================================== RESTART =====================================
    >>> 
             5 function calls in 11.504 seconds    #局部变量測试结果: 11s
    
    
       Ordered by: standard name
    
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    3.135    3.135    3.135    3.135 :0(range)
            1    0.006    0.006    0.006    0.006 :0(setprofile)
            1    0.000    0.000   11.497   11.497 <string>:1(<module>)
            1    8.362    8.362   11.497   11.497 Learn.py:5(param_test)
            1    0.000    0.000   11.504   11.504 profile:0(param_test())
            0    0.000             0.000          profile:0(profiler)
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    while 1 > while True

    while 1运行结果:

    import cProfile
    
    def while_1():
        tag = 0
        while 1:
            tag += 1
            if tag > 100000000:
                break
    
           
    if __name__=='__main__':
        cProfile.run('while_1()')
    
    >>> ===================================== RESTART =====================================
    >>> 
             4 function calls in 5.366 seconds
    
       Ordered by: standard name
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.006    0.006    0.006    0.006 :0(setprofile)
            1    0.000    0.000    5.360    5.360 <string>:1(<module>)
            1    5.360    5.360    5.360    5.360 Learn.py:3(while_1)
            0    0.000             0.000          profile:0(profiler)
            1    0.000    0.000    5.366    5.366 profile:0(while_1())
    while True运行结果:
    import cProfile
    
    def while_true():
        tag = 0
        while True:
            tag += 1
            if tag > 100000000:
                break
           
    if __name__=='__main__':
        cProfile.run('while_true()')
    
    >>> ===================================== RESTART =====================================
    >>> 
             4 function calls in 8.236 seconds
    
       Ordered by: standard name
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.012    0.012    0.012    0.012 :0(setprofile)
            1    0.000    0.000    8.224    8.224 <string>:1(<module>)
            1    8.224    8.224    8.224    8.224 Learn.py:10(while_true)
            0    0.000             0.000          profile:0(profiler)
            1    0.000    0.000    8.236    8.236 profile:0(while_true())
    NOTE: while 1比while True,运行快些,是由于Python 2.x中True相当于全局变量,非keyword.

    尽管前者比后者快些,但后者可读性无疑更好些.

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    求交集集合比列表快

    列表測试结果:

    from time import time
    
    t1 = time()
    list_1 = [32,78,65,99,19,43,18,22,7,1,9,2,4,8,56]
    list_2 = [3,4,8,56,99,100]
    temp   = []
    for x in range(1000000):
        for i in list_2:
            for j in list_1:
                if i == j:
                    temp.append(i)
    t2 = time()
    print "Total time:", t2 - t1
    
    #測试结果:
    >>> 
    Total time: 13.6879999638
    集合測试结果:
    from time import time
    
    t1 = time()
    set_1 = set([32,78,65,99,19,43,18,22,7,1,9,2,4,8,56])
    set_2 = set([3,4,8,56,99,100])
    for x in range(1000000):
        set_same = set_1 & set_2
        
    t2 = time()
    print "Total time:", t2 - t1
    
    #測试结果:
    >>> 
    Total time: 0.611000061035
    NOTE: 用集合的方式取交集速度快的多。

    以下是经常使用的集合操作。


    >>> set1 = set([2,3,4,8,9])  #集合1
    >>> set2 = set([1,3,4,5,6])  #集合2
    >>> set1 & set2              #求交集
    set([3, 4])
    >>> set1 | set2              #求合集
    set([1, 2, 3, 4, 5, 6, 8, 9])
    >>> set1 - set2              #求差集
    set([8, 9, 2])
    >>> set1 ^ set2              #求异或:即排除共同部分
    set([1, 2, 5, 6, 8, 9])
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    直接交换两变量 > 借助中间变量

    要交换X,Y的值。有两种方法:

    1. 直接交换: X, Y = Y, X

    >>> X,Y = 1,2
    >>> X,Y
    (1, 2)
    >>> X, Y = Y, X
    >>> X,Y
    (2, 1)
    2.借助中间变量: T = X, X = Y, Y = X
    >>> X,Y = 1,2
    >>> X,Y
    (1, 2)
    >>> T = X; X = Y; Y = T
    >>> X,Y
    (2, 1)
    測试结果:


    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    is not速度快于!=

    在if条件推断中。能够用 if a is not None:或者 if a != None 前者执行速度快于后者.

    測试结果:


    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ''.join(list)速度快于+或+=

    +測试结果:


    ''.join(list)測试结果:


    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    在循环体外运行函数比在循环中快

    所以要降低函数的调用次数





    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    **比pow()速度快

    測试结果:


    **其速度是pow()函数的几十倍,并且数据越大,越明显。

    **相当于Python的移位操作: 右移(>>) 和 左移(<<)比方说 2**2 = 4相当于 2 << 1 其速度相当!


    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    生成器速度比列表快


    前者是列表解析对象。后者是generate对象。所需的内存空间与列表大小无关。所以速度快些.

    在实际应用中,比方说创建一个集合,用生成器对象明显比列表对象要快些。


    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    浅拷贝的速度比深拷贝速度


    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    cPickle比pickle速度快

    pickle模块和cPickle模块都能够将Python对象永久存储在系统文件里。

    但cPickle是Python的C语言实现。因而速度更快些。

    请看以下对照:


    存储一个百万级别大小的列表,用cPickle模块差点儿10倍于pickle模块。

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    int()比int(math.floor())快

    比方对浮点数取整,32.9,假设要取年龄的话。仅仅能是32。

    能够有两种方式

    一种: int(math.floor(32.9))   # floor先取32.0再转为整数

    一种: int(32.9)                  # 直接向下取整,math.floor()下多余的


    能够看出,另外一种方式要比第一种快的多。

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    读取文件操作for循环比while高效

    測试项目: test.txt 3M的文件


    測试结果: 245.83s


    測试结果: 182.62s

    for line in open('filename'): 

        process(line)

    上面这样的方式应该读取文件的最佳方式:原因有三

    1.写法最简单

    2. 执行最高速

    3. 从内存使用角度来看,也是最好的

    NOTE: 

    1. readlines()是一次性载入全部的行,而xreadlines()是按须要载入。可避免大文件导致内存泄露

    2. readline()是迭代逐行读取。从内存角度来说。在大文件处理中。效率要比readlines()高

    3. 超大文件的话,用readlines()方式能够会导致内存崩溃。

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    字典的迭代取值比直接取值高效

    >>> Timer('for v in d.values(): pass',"d={'a':1,'b':2,'c':3,'d':4,'e':5}").timeit(100000000)
    43.297132594847085
    >>> Timer('for v in d.itervalues(): pass',"d={'a':1,'b':2,'c':3,'d':4,'e':5}").timeit(100000000)
    36.16957129734047
    也就是说:

    # d.itervalues() 比d.values()要快些
    # d.iteritems()  比d.items()要快些
    # d.iterkeys()   比d.keys()要快些
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    创建字典常规方法比工厂方法要快

    常规方法:
    d={'x':1, 'y':2, 'z':3}
    工厂方法:例如以下面几种
    1. dict((['x',1], ['y',2], ['z',3]))

    2. dict(zip(('x', 'y', 'z'), (1, 2, 3)))

    3. dict(x=1, y=2, z=3)

    >>> Timer("d={'x':1, 'y':2, 'z':3}").timeit()
    0.19084989859678103
    >>> Timer("dict((['x',1], ['y',2], ['z',3]))")
    <timeit.Timer instance at 0x0000000002E06288>
    >>> Timer("dict((['x',1], ['y',2], ['z',3]))").timeit()
    1.5503493690009975
    相同工厂方法创建新字典速度比copy()函数慢
    >>> Timer("D2=copy.copy({'x':1, 'y':2, 'z':3})","import copy").timeit()
    1.074277245445046
    >>> Timer("D3=dict((['x',1], ['y',2], ['z',3]))").timeit()
    1.5830155758635556


    展开全文
  • 接上一篇博客《直接法光度误差导数推导》,DSO 代码中 CoarseInitializer::trackFrame 目的是优化两帧(ref frame 和 new frame)之间相对状态和 ...依据这个事实就能够确定整个优化过程所有细节。 一下假设 ref...

    接上一篇博客《直接法光度误差导数推导》,DSO 代码中 CoarseInitializer::trackFrame 目的是优化两帧(ref frame 和 new frame)之间的相对状态和 ref frame 中所有点的逆深度。

    在代码中出现了变量Hsc和变量bsc,其中的"sc"是指 Schur Complement。依据这个事实就能够确定整个优化过程的所有细节。

    一下假设 ref frame 上需要优化逆深度的点共有 N 个。

    首先构建 Gauss Newton 方程,需要优化的参数共有 N + 8 个。

    \[J^TJ \delta x = - J^T r_{21}\]

    其中\(\delta x\)是一个(N+8)x1的向量,\(J\) 是 Nx(N+8) 的矩阵,第 i 行表示\(r_{21}^{(i)}\)\(\delta x\)的导数(求导得到的结果就是“横着”的),\(r_{21}\)是 Nx1 的向量。

    \[\begin{align}\delta x &= \begin{bmatrix} \delta \rho^{(1)} & \delta \rho^{(2)} & \dots & \delta \rho^{(N)} & \delta \xi_{21}^{(1)} & \delta \xi_{21}^{(2)} & \dots & \delta \xi_{21}^{(6)} & \delta a_{21} & \delta b_{21} \end{bmatrix}^T \notag \\ & = \begin{bmatrix} \delta \rho^T & \delta x_{21}^T \end{bmatrix}^T\notag \end{align}\]

    \(x_{21}\) 表示量帧之间的相对状态转换,包括 8 个参数。

    \[\begin{align} J &= \begin{bmatrix} \partial r_{21}^{(1)} \over \partial \rho^{(1)} & \partial r_{21}^{(1)} \over \partial \rho^{(2)} & \dots & \partial r_{21}^{(1)} \over \partial \rho^{(N)} & \partial r_{21}^{(1)} \over \partial \xi_{21}^{(1)} & \partial r_{21}^{(1)} \over \partial \xi_{21}^{(2)} & \dots & \partial r_{21}^{(1)} \over \partial \xi_{21}^{(6)} & \partial r_{21}^{(1)} \over \partial a_{21} & \partial r_{21}^{(1)} \over \partial b_{21} \\ \partial r_{21}^{(2)} \over \partial \rho^{(1)} & \partial r_{21}^{(2)} \over \partial \rho^{(2)} & \dots & \partial r_{21}^{(2)} \over \partial \rho^{(N)} & \partial r_{21}^{(2)} \over \partial \xi_{21}^{(1)} & \partial r_{21}^{(2)} \over \partial \xi_{21}^{(2)} & \dots & \partial r_{21}^{(2)} \over \partial \xi_{21}^{(6)} & \partial r_{21}^{(2)} \over \partial a_{21} & \partial r_{21}^{(2)} \over \partial b_{21} \\ \vdots & \vdots & \ddots & \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ \partial r_{21}^{(N)} \over \partial \rho^{(1)} & \partial r_{21}^{(N)} \over \partial \rho^{(2)} & \dots & \partial r_{21}^{(N)} \over \partial \rho^{(N)} & \partial r_{21}^{(N)} \over \partial \xi_{21}^{(1)} & \partial r_{21}^{(N)} \over \partial \xi_{21}^{(2)} & \dots & \partial r_{21}^{(N)} \over \partial \xi_{21}^{(6)} & \partial r_{21}^{(N)} \over \partial a_{21} & \partial r_{21}^{(N)} \over \partial b_{21} \end{bmatrix} \notag \\ & = \begin{bmatrix} J_\rho & J_{x_{21}} \end{bmatrix}\notag \end{align}\]

    Gauss Newton 方程可以进一步写成

    \[\begin{bmatrix} (J_\rho^TJ_\rho)_{N \times N} & (J_\rho^TJ_{x_{21}})_{N \times 8} \\ (J_{x_{21}}^TJ_\rho)_{8 \times N} & (J_{x_{21}}^TJ_{x_{21}})_{8 \times 8} \end{bmatrix} \begin{bmatrix} \delta \rho \\ \delta x_{21} \end{bmatrix} = - \begin{bmatrix} J_\rho^T r_{21} \\ J_{x_{21}}^T r_{21}\end{bmatrix}\]

    \[\begin{bmatrix} H_{\rho\rho} & H_{\rho x_{21}} \\ H_{\rho x_{21}}^T & H_{x_{21}x_{21}} \end{bmatrix} \begin{bmatrix} \delta \rho \\ \delta x_{21} \end{bmatrix} = - \begin{bmatrix} J_\rho^T r_{21} \\ J_{x_{21}}^T r_{21}\end{bmatrix}\]

    Schur Complement 消除 \(\delta \rho\)

    \[\begin{bmatrix} H_{\rho\rho} & H_{\rho x_{21}} \\ 0 & H_{x_{21}x_{21}} - H_{\rho x_{21}}^TH_{\rho\rho}^{-1}H_{\rho x_{21}} \end{bmatrix} \begin{bmatrix} \delta \rho \\ \delta x_{21} \end{bmatrix} = - \begin{bmatrix} J_\rho^T r_{21} \\ J_{x_{21}}^T r_{21} - H_{\rho x_{21}}^TH_{\rho\rho}^{-1} J_\rho^T r_{21} \end{bmatrix}\]

    CoarseInitializer::trackFrame 是调用了 CoarseIntializer::calcResAndGS 计算 Gauss Newton 相关的数值。
    在函数 CoarseIntializer::calcResAndGS 中变量acc9与公式中的\(H_{x_{21}x_{21}}, J_{x_{21}}^T r_{21}\)相关,acc9SC与公式中的\(H_{\rho x_{21}}^TH_{\rho\rho}^{-1}H_{\rho x_{21}}, H_{\rho x_{21}}^TH_{\rho\rho}^{-1} J_\rho^T r_{21}\)相关。

    acc9.H是9x9的矩阵:

    \[\begin{bmatrix} J_{x_{21}}^TJ_{x_{21}} & J_{x_{21}}^Tr_{21} \\ J_{x_{21}} r_{21} & r_{21}r_{21} \end{bmatrix}\]

    acc9的累加在两个循环内部,循环是遍历 patternNum。

    第一个循环 for(int i=0;i+3<patternNum;i+=4) 内部是 acc9.updateSSE
    acc9.updateSSE 使用了 Intel 的 Streaming SIMD Extensions (SSE),一次操作取 4 个 float,完成 4 个不同数据、相同命令的 float 运算。这种一次 4 个数据的操作,说明了循环的 i 增量是 4。算法现在使用的 patternNum 是 8(8%4==0),所以当前循环可以完成所有 pattern 点的操作。如果 patternNum%4 != 0,剩下的余数就可以用第二个循环 for(int i=((patternNum>>2)<<2); i < patternNum; i++)((patternNum>>2)<<2)把最后两个 bit 设置为 0,得到了小于 patternNum 的最大的 4 的倍数,这个循环每次进行 1 个float运算,把最后未循环到的 pattern 点补足。

    为了理解acc9SC需要将前面的两个舒尔补矩阵进行变换:

    \[\begin{align}H_{\rho x_{21}}^TH_{\rho\rho}^{-1}H_{\rho x_{21}} &= (J_\rho^TJ_{x_{21}})^T (J_\rho^TJ_\rho)^{-1}J_\rho^TJ_{x_{21}}\notag \\ &= {1 \over J_\rho J_\rho^T} J_{x_{21}}^TJ_\rho (J_\rho^TJ_\rho)^{-1} J_\rho^T J_\rho J_\rho^T J_{x_{21}} \notag \\ &= {1 \over J_\rho J_\rho^T} J_{x_{21}}^TJ_\rho J_\rho^T J_{x_{21}} \notag \end{align}\]

    \[\begin{align} H_{\rho x_{21}}^TH_{\rho\rho}^{-1} J_\rho^T r_{21} &= (J_\rho^TJ_{x_{21}})^T (J_\rho^TJ_\rho)^{-1}J_\rho^Tr_{21} \notag \\ &= {1 \over J_\rho J_\rho^T} J_{x_{21}}^TJ_\rho J_\rho ^Tr_{21} \notag \end{align}\]

    JbBuffer_new在 idx pattern 循环内,分别对每点的8个 pattern 的\(J_{x_{21}}^TJ_\rho, J_\rho^Tr_{21}, J_\rho J_\rho^T\)进行累加。最后在 idx 点循环完成之后,使用JbBuffer_new中的数据对acc9SC进行处理,累加得到最终的结果。在处理的过程中代码

    JbBuffer_new[i][9] = 1 / (1 + JbBuffer_new[i][9]);

    对应着\({1 \over J_\rho J_\rho^T}\),分母里多了一个1,猜测是为了防止JbBuffer_new[i][9]太小造成系统不稳定。

    回到函数 CoarseInitializer::trackFrame 中,在得到相关的矩阵之后,变量H, Hsc相减解算\(x_{21}\),对Hsc加了一个权值1/(1+lambda),并且在接受更新时降低lambda提高了Hsc的权重。这个权重相当于是\(d\)对于\(x_{21}\)的权重。

    \(x_{21}\)的更新很容易看到,对\(d\)的更新在 CoarseInitializer::doStep 中。

    float b = JbBuffer[i][8] + JbBuffer[i].head<8>().dot(inc);
    float step = -b * JbBuffer[i][9] / (1 + lambda);

    在得到\(\delta x_{21}\)之后可以代回求\(\delta \rho\)

    \[ H_{\rho\rho}\delta \rho + H_{\rho x_{21}}\delta x_{21} = -J_\rho^Tr_{21} \\ \delta \rho = -H_{\rho\rho}^{-1}(J_\rho^Tr_{21} + H_{\rho x_{21}}\delta x_{21})\]

    对于每一个点,代码中inc对应\(\delta x_{21}\)JbBuffer[i].head<8>()对应\(H_{\rho x_{21}}\)的第 8*i 行到第 8*i+7 行,JbBuffer[i][8]对应\(J_\rho^Tr_{21}\)的第 8*i 行到第8*i+7 行,JbBuffer[i][9]对应\(H_{\rho\rho}^{-1}\)\((i, i)\)位置上的 Scalar。

    转载于:https://www.cnblogs.com/JingeTU/p/8297076.html

    展开全文
  • 最近,我给Java项目做了一次代码清理工作。...这份列表没有依据任何规则或顺序,所有这些都是通过代码质量工具包括CheckStyle,FindBugs和PMD检查出。一起来看下:一、Eclipse编译器提供源代码格...
  • 代码优化1:使用条件数据传送代替条件控制 本文是阅读深入理解计算机系统第六章随笔,也就是如何优化代码.本来最开始谈谈如何优化循环,后来发现这个问题太大了,所以先讲讲简单条件数据传送 条件控制转移 条件控制...
  • 看了卡巴斯基(ms就是那个写杀毒软件大牛)写一本书《代码优化-高效使用内存》,Code Optimization: Effective Memory Usage主要是针对C/C++,但通用性还比较强,不但讲了怎么样来做代码优化,而且还讲了为...
  • 现要求依据运输功最小配矿,达到目标品位。 品位:就是岩石中铁含量(eg:24.5%) (目标函数)min运输功:距离(采矿点到破碎站距离)*矿量(采矿点出矿量) 2.一个矿坑一次最多开采6个水平,每个水平上最多...
  • 选择题 列优化技术是基本块内优化为( B)。...代码优化的依据: 等价变换规则 代码生成阶段主要任务是( 把中间代码变换成依赖具体机器目标代码) 经编译得到目标程序是: 机器语言程序或者汇编语言程序...
  • 很多站长使用虚拟主机来做网站,网页内容一旦很多,网站打开速度就会特别慢,如果说服务器、带宽、CDN这类硬指标我们没有经济实力去做,不妨通过网页代码优化的方式来提高速度,卢松松总结了一些可行性方法。...
  • 什么是优雅的代码

    千次阅读 2015-05-05 23:03:30
    我喜欢优雅和高效...性能调制最优,省引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。 –bjarne stroustrup(《c++程序设计语言》作者)简洁的代码简单直接。简洁的代码如同优美散文。简洁
  • 依据图片可知,我们可以建立三个类,一个是pet类,一个是dog类,还有一个penguin类,且pet类是dog类和penguin类父类。 实现代码如下: //Pet类 public class Pet { private String name;//名字 private int health...
  • 控制层:校验数据合法,并依据request从cookies中取出用户城市站、usertraceId等信息,根据request请求头判断设备类型。最终封装实体调用入库接口。 服务层:验重、保存数据。 问题: 控制层做入参校验理所当然,...
  • 有多少程序员,就有多少定义。所以我只询问了一些非常知名且经验丰富...性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。 Bjarne用了“优雅”一词。说得好!我MacBook上词典.
  • 天牛须搜索算法(beetle antennae search,BAS)算法是2017年提出一种基于天牛觅食原理适用于多目标函数优化的新技术,其生物原理为:当天牛觅食时,,其并不知道食物在哪里,而是根据食物气味强弱来觅食。...
  • 什么是整洁代码

    2019-10-02 11:02:26
    依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。二、整洁的代码简单直接。整洁的代码如同优美散文。整洁的代码从不隐藏设计者意图,...
  • 优化器(optimizer)是...依据所选择执行计划时所用判断原则,oracle数据库里的优化器又分为RBO(基于原则的优化器)和CBO(基于成本的优化器,SQL成本根据统计信息算出)两种。 一、RBO Oracle会在代码里...
  • 性能优化指南:性能优化的一般性原则与方法 目录 一般性原则 ... 性能优化代码质量 总结 正文  作为一个程序员,性能优化是常有事情,不管是桌面应用还是web应用,不管是前端还是后..
  • sql 优化之多表联合查询干掉 “distinct” 去重关键字在我提交了代码的时候,架构师给我指出我这个sql这样写会有问题。因为在分库分表时候,是不支持子查询。所以需要把多表子查询 sql 结构进行优化。是不是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 447
精华内容 178
关键字:

代码优化依据的是