a good example of where dynamic/jit is good is sqlite api.
with dynamic/jit, we can compile a function that is specific to the table layout and column types of the query we are running. we can eliminate code this way.
if we implement this in C or some statically compiled language then we have to use the same logic for every query, which is less efficient.
e.g.
get () {
const { types, names, stmt } = this
if(this.step() === ROW) {
const { columns } = this
const row = {}
for (let i = 0; i < columns; i++) {
if (types[i] === 1) {
row[names[i]] = column_int(stmt, i)
} else if (types[i] === 2) {
row[names[i]] = column_double(stmt, i)
} else if (types[i] === 3) {
row[names[i]] = this.columnText(i)
} else if (types[i] === 4) {
row[names[i]] = this.columnBlob(i)
}
}
this.reset()
return row
}
}
this is the generic function that works for all queries.
but, if we know the types, which we pretty much always do with SQL, we can eliminate all the branching and produce this:
get () {
const { types, names, stmt } = this
const row = new foo_num()
if (sqlite.step(stmt) === 100) {
row['Id'] = sqlite.column_int(stmt, 0)
row['CustomerId'] = this.columnText(1)
row['EmployeeId'] = sqlite.column_int(stmt, 2)
row['OrderDate'] = this.columnText(3)
row['RequiredDate'] = this.columnText(4)
row['ShippedDate'] = this.columnText(5)
row['ShipVia'] = sqlite.column_int(stmt, 6)
row['Freight'] = sqlite.column_double(stmt, 7)
row['ShipName'] = this.columnText(8)
row['ShipAddress'] = this.columnText(9)
row['ShipCity'] = this.columnText(10)
row['ShipRegion'] = this.columnText(11)
row['ShipPostalCode'] = this.columnText(12)
row['ShipCountry'] = this.columnText(13)
}
sqlite.reset(stmt)
return row
}
this eliminates a loop through the resultset columns and from one to four indexes into the types array and another index into the names array inside the loop.
and this:
all () {
const rows = []
const { stmt } = this
let rc = step(stmt)
//let rc = this.step()
let count = 0
if (!this.types.length) {
this.columns = column_count(stmt)
for (let i = 0; i < this.columns; i++) {
this.names.push(utf8Decode(column_name(stmt, i), -1))
this.types.push(column_type(stmt, i))
}
}
const { columns, names, types } = this
while (rc === ROW) {
const row = {}
for (let i = 0; i < columns; i++) {
// todo: these could be indexes into a function table
if (types[i] === 1) {
row[names[i]] = column_int(stmt, i)
} else if (types[i] === 2) {
row[names[i]] = column_double(stmt, i)
} else if (types[i] === 3) {
row[names[i]] = this.columnText(i)
} else if (types[i] === 4) {
row[names[i]] = this.columnBlob(i)
}
}
rows.push(row)
count++
rc = step(stmt)
}
assert(rc === OK || rc === DONE)
this.count = count
this.reset()
return rows
}
becomes this:
all () {
const { types, names, cols, stmt } = this
const rows = []
let rc = sqlite.step(stmt)
let i = 0
while (rc === 100) {
const row = new foo_num()
row['Id'] = sqlite.column_int(stmt, 0)
row['CustomerId'] = this.columnText(1)
row['EmployeeId'] = sqlite.column_int(stmt, 2)
row['OrderDate'] = this.columnText(3)
row['RequiredDate'] = this.columnText(4)
row['ShippedDate'] = this.columnText(5)
row['ShipVia'] = sqlite.column_int(stmt, 6)
row['Freight'] = sqlite.column_double(stmt, 7)
row['ShipName'] = this.columnText(8)
row['ShipAddress'] = this.columnText(9)
row['ShipCity'] = this.columnText(10)
row['ShipRegion'] = this.columnText(11)
row['ShipPostalCode'] = this.columnText(12)
row['ShipCountry'] = this.columnText(13)
rows.push(row)
rc = sqlite.step(stmt)
i++
}
this.count = i
sqlite.reset(stmt)
return rows
}
this eliminates the same loop as the get
method but in this case it is an inner loop when iterating through a result set for the all
method. we also don't need to do the check for the column types on the first run of the statement. if these columns were not cached in the original all
implementation, then we would have to pay the price of iterating them on every query but the dynamic method won't.