使用缓式评估

转自:http://xiangxiaojiang.blog.163.com/blog/static/268961772007849414911/

 

缓式评估(lazy evaluation,延迟求值)是一项与语言无关的技术,它会延缓计算,直到不能再拖为止。工作的延迟当然不会带来性能的提升,但是如果被推迟的计算后来不再需要,从而不需执行的话,性能就提升了。也就是说免除了非必要的工作。

        考虑一个用来代表员工(employee)的大型对象实例。这个对象内含许多值域,其中一些又是对象,内含与员工相关的各项数据。在创建这个对象的时候,需要指定独一无二的识别码作为数据库主键(key)。数据库内包含了创建这个对象所需的一切信息。请看Employee class及其相关的辅助类(support classes):

class NameInfo
{
  private String lastName;
  private String firstName;
  private String middleName;
  private String courtesyTitle;
  //...
}

class ContactNumbers
{
  private String homeNumber;
  private String officeNumber;
  private String mobileNumber;
  private String faxNumber;
  //...
}

class EmergencyContactInfo
{
  private NameInfo name1;
  private ContactNumbers number1;
  private NameInfo name2;
  private ContactNumbers number2;
  //...
}

class AddressInfo
{
  private String street;
  private String city;
  private String state;
  private int    zip;
  //...
}

class WorkAddressInfo
{
  private AddressInfo workAddress;
  private String mailStop;
  //...
}

class Employee
{
  private NameInfo name;
  private String jobTitle;
  private String emailAddress;
  private ContactNumbers phoneNumbers;
  private EmergencyContactInfo emergencyContact;

  private AddressInfo homeAddress;
  private WorkAddressInfo workAddress;

  public Employee(int employeeID)
  {
    //Make multiple queries to a database to gather the
    //information about an Employee. Then build all of the parts
    //of the Employee object with the data.
    name = new NameInfo(...);
    jobTitle = new String(...);
    emailAddress = new String(...);
    phoneNumbers = new ContactNumbers(...);
    emergencyContact = new EmergencyContactInfo(...);
    homeAddress = new AddressInfo(...);
    workAddress = new WorkAddressInfo(...);
    //...
  }

  public AddressInfo homeAddr()
  {
    return homeAddress;
  }
  //...
}

如上所示,在创建Employee对象之前,需先读取数据库,并先创建数个聚合对象(aggregate objects)。Praxis32讨论过对象创建的代价。

        假设当创建Employee对象时并不需要用到相关所有数据。例如当写下这段代码:

public void printHomeAddress(int employeeID) {
    Employee emp = new Employee(employeeID);
    AddressInfo addrinfo = emp.homeAddr();
    System.out.println("Street " + addrinfo.street());
    System.out.println("City " + addrinfo.city());
    System.out.println("State " + addrinfo.state());
    System.out.println("Zip " + addrinfo.zip());
}

这段代码支付了创建Employee对象的全部开销,但是在超出函数作用域(method scope)之前,它只使用了一个值域(就是homeAddress)。我们的目标是降低对象的创建成本,那么,消减这一类开销的途径之一就是利用缓式评估(延迟求值)技术,令Employee对象的创建动作更廉价。

        通过缓式评估技术,让我们将各种计算推迟到最后必要关头。对Employee而言,意味不在其构造函数中建构完整对象,而是先将所有值域(都是object reference)初始化为null。当对一个Employee对象调用其成员函数时,这时候才建构出对象的必要部分以满足函数调用。运用这样的思路,Employee及其构造函数修改如下:

class Employee
{
  private NameInfo name;
  private String jobTitle;
  private String emailAddress;
  private ContactNumbers phoneNumbers;
  private EmergencyContactInfo emergencyContact;
  private AddressInfo homeAddress;
  private WorkAddressInfo workAddress;
  private int employeeID;

  public Employee(int eid)
  {
    employeeID = eid;
  }
  //...
}

在创建Employee对象时,构造函数的惟一功能就是在class的instance数据中存储employeeID值。这使得Employee对象的创建速度提升不少。其他的instance数据都被设置成null——这不需要构造函数亲自动手,因为null是这些值域的缺省值。在初始化某个class时,JVM会在调用构造函数本体之前先将所有instance变量设置为相应缺省值(参见Praxis37)。

        对构造函数做了这样的修改之后,Employee的函数也需要改动。例如homeAddr()做了如下修改:

public AddressInfo homeAddr()
  {
    if (homeAddress == null)
    {
      //query database based on stored employeeID and gather
      //information to create the AddressInfo object.
    }
    return homeAddress;
  }

这样,通过采用缓式评估技术,不再需要收集用不上的数据,从而得以节省打量时间。于是Employee对象的创建工作得以从非常昂贵变得非常低廉。

        这项技术同意可以运用于数字计算。某些计算格外花费时间,通过缓式评估(延迟求值)的巧妙运用,往往可以显著加速程序的执行速度。考虑下面这个为矩阵(matrix)设计的class,其中含有矩阵相加函数和相乘函数:

 class Matrix2D
{
  private int[][] matrix;

  public Matrix2D(int sizex, int sizey)
  {
    matrix = new int[sizex][sizey];
  }
  public void addMatrix(Matrix2D other)
  {
    //add other to this.
  }
  public void multiplyMatrix(Matrix2D other)
  {
    //multiply other by this.
  }
  public int elementAt(int row, int col)
  {
    //return element at row/col
  }
  //...
}

下面是这个class的一些用法:

Matrix2D matrix1 = new Matrix2D(500,500);
Matrix2D matrix2 = new Matrix2D(500,500);
matrix1.multiplyMatrix(matrix2);
matrix1.elementAt(275, 314);

这段代码创建出两个矩阵,将二者相乘,然后取回250000个元素中的一个。如果其他249999个元素在“结果矩阵”超出生存空间(作用域,scope)之前没有被用到,两个矩阵的相乘工作就几乎白费了。即便有四分之三的矩阵元素被访问,仍然浪费了62500个相乘动作。这意味着CPU资源的巨大浪费。

        较好的解决方案是采用缓式评估技术。在multiplyMatrix()或addMatrix()执行的时候,不对矩阵进行完全的相加或相乘。不,什么都不做,之保存“施加于矩阵身上之动作形式”的相关信息。然后,当程序以elementAt()取出矩阵某些区域的时候,才去计算(和返回)必要数据。这种方式只做必要工作,略去无谓劳动。下面是实现这项技术的一个范例:

class Matrix2D
{
  private int[][] matrix;
  private boolean performAdd;
  private boolean performMultiply;
  private Matrix2D mat;
  private boolean actionPending;
  private int width;
  private int height;

  public Matrix2D(int sizex, int sizey, int initialValue)
  {
    width = sizex;
    height = sizey;
    matrix = new int[sizex][sizey];
    for (int i=0; i<sizex; i++)
      for (int j=0; j<sizey; j++)
        matrix[i][j] = initialValue;
  }
  public void addMatrix(Matrix2D other)
  {
    if (actionPending)
      performAction();
    performAdd = true;
    performMultiply = false;
    mat = other;
    actionPending = true;
  }
  public void multiplyMatrix(Matrix2D other)
  {
    if (actionPending)
      performAction();
    performMultiply = true;
    performAdd = false;
    mat = other;
    actionPending = true;
  }
  public int elementAt(int row, int col)
  {
    int value;
    if (performAdd)
      value = (mat.matrix[row][col]) + (this.matrix[row][col]);
    else
      value = (mat.matrix[row][col]) * (this.matrix[row][col]);

    actionPending = false;
    return value;
  }
  private void performAction()
  {
    if (performAdd)
    {
      for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
          matrix[i][j] += mat.matrix[i][j];
    }
    else if (performMultiply)
    {
      for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
          matrix[i][j] *= mat.matrix[i][j];
    }
  }
  //...
}

        上述代码只是为了说明这项技术的使用,因此省略了一些错误检查。注意,这段代码在一个时刻只保留一个动作轨迹。例如,假设你创建了两个Matrix2D对象。第一个对象调用addMatrix()并传入第二个矩阵对象。在这次调用中,addMatrix()仅仅只是存储“矩阵相加”信息。如果在调用elementAt()之前调用了addMatrix()或multiplyMatrix(),则先执行“未决动作”(pending action),而后才存储新动作(的相关信息)。如果希望程序更加高效,则可以添加额外代码来存储更多的未决动作。

        采用这项技术可以减少计算量,带来明显的性能收益。但是请记住,缓式评估(延迟计算)技术并非总是能够带来更快速的代码。以上述矩阵为例,如果矩阵的所依数值都将被访问,那么缓式评估技术反而会使程序速度迟缓,因为这项技术需要额外的数据和逻辑。只有在“为满足性能目标,你可以免除够多的非必要计算”情况下,缓式评估才是有益的。其基本策略就是“延缓那些很可能永远也不需要进行的工作”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值