Skip to content

Resource Utilization

sandal edited this page Dec 14, 2011 · 9 revisions

While it is convenient to imagine that our software works in isolation and rests on top of infinitely scalable hardware,most software projects need to take into account that their available resources are limited. When we think of efficient resource utilization, we typically think about reducing memory footprints or disk space usage. However, the concept of using resources efficiently can be applied to more transient things as well, such as file locks. The PStore standard library provides a nice example of how to deal with this scenario gracefully.

A PStore object is essentially nothing more than a file-backed Hash that is transactionally aware. By default, PStore transactions set an exclusive lock on the file they write to, to prevent other processes from modifying the contents while the transaction is running. This is in general a good thing, because it helps with data integrity. Without this sort of locking, the code below would be downright dangerous to run in a multiprocessing environment:

require "pstore"

class Account
  def initialize(name)
    @store = PStore.new("#{name}.pstore")
    @store.transaction do
      @store[:balance] ||= 0
    end
  end

  def withdraw(amount)
    @store.transaction do
      new_balance = @store[:balance] - amount
      raise "Insufficient funds" if new_balance == 0
      @store[:balance] = new_balance
    end
  end

  def deposit(amount)
    @store.transaction do
      @store[:balance] += amount
    end
  end
end

However, for read-only operations, setting up an exclusive lock is inefficient. For this reason, PStore allows you to pass a boolean flag to your transaction to indicate it is read-only. In addition to enforcing this access control on the hash structure itself, this changes the locking behavior to use shared locks, allowing read operations to run concurrently. The Account#balance method below demonstrates a bit of read-only code that makes use of this functionality.

class Account
  # ... other code as before ...

  def balance
    @store.transaction(true) do
      @store[:balance] 
    end
  end
end

This example hopefully hammers the point home that efficient resource utilization is about more than just space and memory constraints, but is also applicable to the myriad ways in which your software interacts with its environment. High quality software tends to take only what it needs, so that it can coexist peacefully with other processes.


Turn the page if you're taking the linear tour, or feel free to jump around via the sidebar.