Skip to content

Instantly share code, notes, and snippets.

@paulcuth
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulcuth/2b6ecf8817a12a8fd5dd to your computer and use it in GitHub Desktop.
Save paulcuth/2b6ecf8817a12a8fd5dd to your computer and use it in GitHub Desktop.

This is a little experiment and my first Lua project aimed at performance in LuaJIT.

Most of the code is bootstrap, but at the bottom you'll find some tests followed by some benchmarking code.

I'm surprised to find that the benchmarking code runs 4x faster with the tests included than when they're removed. In fact, just removing the print() statement at the end of the tests makes the benchmark run a lot slower. Why is this?

(Unfortunately, I haven't been able to reduce the code sample and still maintain the same behaviour.)

local bit = require 'bit'
-- Primatives
null = {}
undefined = {}
-- Abstract functions
-- http://www.ecma-international.org/ecma-262/5.1/#sec-9.12
function isSameValue(a, b)
-- todo
return a == b
end
-- Custom
function isPrimitive(val)
if val == undefined or val == null then
return true
end
local t = type(val)
return t ~= 'table'
end
-- todo objects
TypeError = {}
-- Object
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.6
Object = {}
Object.__index = Object
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.6.2
function Object:new ()
local o = {
prototype = nil,
class = 'Object',
extensible = true,
_properties = {},
_propertyFlags = {}
}
return setmetatable(o, self)
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.1
function Object:getOwnProperty (propertyName)
local band = bit.band
local flags = self._propertyFlags[propertyName]
-- 1
if flags == nil then
return
end
-- 2
local newFlags = 0
-- 3
local value = self._properties[propertyName]
-- 4
if band(flags, 1--[[TYPE]]) == 0--[[DATA]] then
newFlags = 2--[[WRITABLE]]
end
-- 5 NOOP
-- 6, 7
newFlags = bit.bor(newFlags, band(flags, 12--[[ENUMERABLE + CONFIGURABLE]]))
-- 8
return value, flags
end
--http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.2
function Object:getProperty (propertyName)
-- 1
local value, flags = self:getOwnProperty(propertyName)
-- 2
if flags ~= nil then
return value, flags
end
-- 3
local proto = self.prototype
-- 4
if proto == nil then
return
end
-- 5
return proto:getProperty(propertyName)
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.3
function Object:get (propertyName)
-- 1
local value, flags = self:getProperty(propertyName)
-- 2
if flags == nil then
return
end
-- 3
if bit.band(flags, 1--[[TYPE]]) == 0--[[DATA]] then
return value
end
-- 4
local getter = value[1--[[GETTER]]]
-- 5
if getter == nil then
return
end
-- 6
return getter(self)
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.4
function Object:canPut (propertyName)
local band = bit.band
-- 1
local value, flags = self:getOwnProperty(propertyName)
-- 2
if flags ~= nil then
-- a
if band(flags, 1--[[TYPE]]) == 1--[[ACCESSOR]] then
return value[2--[[SETTER]]] ~= nil
end
-- b
return band(flags, 2--[[WRITABLE]]) == 1
end
-- 3
local proto = self.prototype
-- 4
if proto == nil then
return self.extensible
end
-- 5
local inheritedValue, inheritedFlags = proto:getProperty(propertyName)
-- 6
if inheritedFlags == nil then
return self.extensible
end
-- 7
if band(inheritedFlags, 1--[[TYPE]]) == 1--[[ACCESSOR]] then
return inherited[2--[[SETTER]]] ~= nil
end
-- 8a
if not self.extensible then
return false
end
-- 8b
return band(inheritedFlags, 2--[[WRITABLE]]) == 1
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.5
function Object:put (propertyName, newValue, throw)
-- 1
if not self:canPut(property) then
-- a
if throw then
error(TypeError)
end
-- b
return
end
local band = bit.band
-- 2
local currentValue, currentFlags = self:getOwnProperty(propertyName)
-- 3
if currentFlags ~= nil and band(currentFlags, 1--[[TYPE]]) == 0--[[DATA]] then
-- a, b
self:defineOwnProperty(propertyName, newValue, 0, 0, throw)
-- c
return
end
-- No need to lookup property a second time if it was found the first.
if currentFlags == nil then
-- 4
currentValue, currentFlags = self:getProperty(propertyName)
end
-- 5
if currentFlags ~= nil and band(currentFlags, 1--[[TYPE]]) == 1--[[ACCESSOR]] then
-- a, b
value[2--[[SETTER]]](self, newValue)
-- 7
return
end
-- 6
self:defineOwnProperty(propertyName, newValue, 14, 14, throw) -- 2--[[WRITABLE]] + 4--[[ENUMERABLE]] + 8--[[CONFIGURABLE]]
-- 7
return
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.6
function Object:hasProperty (propertyName)
-- 1
local _, flags = self:getProperty(propertyName)
-- 2, 3
return flags ~= nil
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.7
function Object:delete (propertyName, throw)
-- 1
local _, flags = self:getOwnProperty(propertyName)
-- 2
if flags == nil then
return true
end
-- 3
if bit.bind(flags, 8--[[CONFIGURABLE]]) == 1 then
-- a
self._properties[propertyName] = nil
self._propertyFlags[propertyName] = nil
-- b
return true
end
-- 4
if throw then
error(TypeError)
end
-- 5
return false
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8
function Object:defaultValue (hint)
if hint == 'string' then
return self:_defaultValueString()
elseif hint == 'number' then
return self:_defaultValueNumber()
elseif self.class == 'Date' then
return self:_defaultValueString()
end
return self:_defaultValueNumber()
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8
function Object:_defaultValueString ()
-- 1
local toString = self:get 'toString'
-- 2
if type(toString) == 'function' then
-- a
local str = toString(self)
-- b
if isPrimative(str) then
return str
end
end
-- 3
local valueOf = self:get 'valueOf'
-- 4
if type(valueOf) == 'function' then
-- a
local val = valueOf(self)
-- b
if isPrimative(val) then
return val
end
end
-- 5
error(TypeError)
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8
function Object:_defaultValueNumber ()
-- 1
local valueOf = self:get 'valueOf'
-- 2
if type(valueOf) == 'function' then
-- a
local val = valueOf(self)
-- b
if isPrimative(val) then
return val
end
end
-- 3
local toString = self:get 'toString'
-- 4
if type(toString) == 'function' then
-- a
local str = toString(self)
-- b
if isPrimative(str) then
return str
end
end
-- 5
error(TypeError)
end
-- http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.9
function Object:defineOwnProperty (propertyName, newValue, newFlags, newFlagMask, throw)
local band = bit.band
-- 1
local currentValue, currentFlags = self:getOwnProperty(propertyName)
-- 2 NOOP
-- 3
if currentFlags == nil then
if not self.extensible then
if throw then
error(TypeError)
else
return false
end
elseif band(newFlags, 1--[[TYPE]]) == 0--[[DATA]] or band(newFlagMask, 1--[[TYPE]]) == 0--[[NOT_SET]] then
-- 4a
self._properties[propertyName] = newValue
self._propertyFlags[propertyName] = band(newFlags, 14--[[WRITABLE + ENUMBERABLE + CONFIGURABLE]])
else
-- 4b
self._properties[propertyName] = newValue
self._propertyFlags[propertyName] = band(newFlags, 12--[[ENUMBERABLE + CONFIGURABLE]])
end
-- 4c
return true
end
-- 5
if newValue == nil and newFlagMask == 0 then
return true
end
-- 6
-- mask may need to be used in the first condition
if currentFlags == newFlags and isSameValue(currentValue, newValue) then
return true
end
local currentConfigurable = band(currentFlags, 8--[[CONFIGURABLE]])
-- 7
if currentConfigurable == 0 then
-- a, b
if band(newFlags, 8--[[CONFIGURABLE]]) == 1 or band(currentFlags, 4--[[ENUMERABLE]]) ~= band(newFlags, 4--[[ENUMERABLE]]) then
if throw then
error(TypeError)
else
return false
end
end
end
-- 8
if band(newFlagMask, 1--[[TYPE]]) == 0--[[NOT_SET]] then
-- NOOP
else
local currentType = band(currentFlags, 1--[[TYPE]])
-- 9
if currentType ~= band(newFlags, 1--[[TYPE]]) then
local currentWritable = band(currentFlags, 2--[[WRITABLE]])
-- a
if currentConfigurable == 0 then
if throw then
error(TypeError)
else
return false
end
end
if currentType == 0--[[DATA]] then
-- b
self._propertyFlags[propertyName] = band(newFlags, 12) + 1 -- current(8--[[CONFIGURABLE]] + 4--[[ENUMERABLE]]) + 1--[[ACCESSOR]]
else
-- c
self._propertyFlags[propertyName] = band(newFlags, 12) -- current(8--[[CONFIGURABLE]] + 4--[[ENUMERABLE]]) + 0--[[DATA]]
end
elseif currentType == 0--[[DATA]] and band(newFlags, 1--[[TYPE]]) == 0--[[DATA]] then
-- 10
-- a
if currentConfigurable == 0 then
-- i
if currentWritable == 0 then
if band(newFlags, 2--[[WRITABLE]]) == 1 then
if throw then
error(TypeError)
else
return false
end
end
-- ii
if newValue ~= nil and not isSameValue(newValue, currentValue) then
if throw then
error(TypeError)
else
return false
end
end
end
end
-- b NOOP
elseif currentType == 1--[[ACCESSOR]] and band(newFlags, 1--[[TYPE]]) == 1--[[ACCESSOR]] then
-- 11
-- a
if currentConfigurable == 0 then
local newSetter = newValue[2--[[SETTER]]]
local newGetter = newValue[1--[[GETTER]]]
if (
-- i
newSetter ~= nil and not isSameValue(newSetter, currentValue[2--[[SETTER]]])
) or (
-- ii
newGetter ~= nil and not isSameValue(newGetter, currentValue[1--[[GETTER]]])
) then
if throw then
error(TypeError)
else
return false
end
end
end
end
end
-- 12
self._properties[propertyName] = newValue
-- 13
return true
end
-- Tests
local x = Object:new()
assert(x ~= nil, 'Object:new should create a new Object')
assert(x:get('moo') == nil, 'Object properties should be initialised nil')
x:put('moo', 'hello')
assert(x:get('moo') == 'hello', 'Object properties should return given data value [1]')
x:put('hello', 'moo')
assert(x:get('hello') == 'moo', 'Object properties should return given data value [2]')
assert(x:get('moo') == 'hello', 'Setting Object properties should not affect existing properties')
x:put('moo', 2)
assert(x:get('moo') == 2, 'Object properties with data values should be able to be overitten')
print 'ok'
-- Env
Date = Object:new()
Date:put('now', function ()
return os.clock() * 1000
end)
console = Object:new()
console:put('log', print)
-- Translated (auto-generated) code:
local _objget=Object.get
local _objput=Object.put
local _objnew=Object.new
local xtotal=0
local ytotal=0
local start=_objget(Date,"now")()
local f=0
while (f<1000) do
local x=_objnew(Object)
_objput(x,"moo",1)
xtotal=xtotal+_objget(x,"moo")
local y=_objnew(Object)
_objput(y,"moo",1)
_objput(x,"moo",y)
local z=_objget(x,"moo")
ytotal=ytotal+_objget(z,"moo")
f=f+1
end
_objget(console,"log")(xtotal,ytotal,_objget(Date,"now")()-start)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment