Arkiv för kategori ‘Kod’

JavaScriptprestanda

För moderna webbapplikationer där en större del av koden körs i webbläsaren blir det allt viktigare med bra prestanda för exekvering av JavaScript.
För att kontrollera text i formulärfält och liknande simpla uppgifter är inte hastigheten så avgörande, men vad händer om man försöker köra tyngre beräkningar? För att göra ett benchmark som testar prestandan i de vanligaste webbläsarna har jag skrivit ett program som löser Sudokus.

Ett program som löser Sudoku med JavaScript

För att jämföra de olika webbläsarna har jag kört denna Sudoku-kombination och jämfört tiden det tar att räkna fram alla möjliga lösningar på just min dator. Resultatet är ganska häpnadsväckande, som ni kan se i grafen nedan så utklassar Safari Internet Explorer med nästan en faktor på tio-till-ett. Chrome hamnar inte långt efter och både Firefox och Opera placerar sig hyffsat bra. Och då används inte ens Web Worker-trådar som har visat sig ha potential att dubbla prestandan och ytterligare öka försprånget för alla andra webbläsare framför Internet Explorer.

Tid för att lösa ett Sudoku med JavaScript i olika webbläsare

Hpricot och Sinatra på Google App Engine

Hpricot är en HTML parser som är skriven i Ruby. Jag gillar den eftersom den är snabb och extremt enkel att jobba med. Den är perfekt om man vill extrahera innehåll från en webbsida som inte tillhandahåller ett färdigt api. Det finns många bra tutorials på nätet.

Ett enkelt exempel

För att till exempel hitta alla nyheter på Athegas första sida kan man göra så här.

require 'rubygems'
require 'open-uri'
require 'hpricot'
 
# Läs in Athegas första sida
doc = Hpricot(open("http://athega.se"))
# Xpath uttryck för att hitta nyheterna
result = doc/"//*[@id='helplist']/li/a"

Hpricot på Google App Engine

Jag ville använda Hpricot tillsammans med Jruby och Sinatra (som Peter har skrivit mer om) på Google App Engine. Jag följde den här guiden för att komma igång med min Sinatra applikation på App Engine  och det gick smärtfritt. Tyvärr så small det direkt när jag försökte använda mig av Hpricot. Ett AccessControlException kastades.

javax.servlet.ServletContext log: Application Error
java.security.AccessControlException: access denied (java.net.SocketPermission athega.se resolve)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
at java.security.AccessController.checkPermission(AccessController.java:546)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:128)
at java.lang.SecurityManager.checkConnect(SecurityManager.java:1031)
at java.net.InetAddress.getAllByName0(InetAddress.java:1145)
at java.net.InetAddress.getAllByName(InetAddress.java:1083)
at java.net.InetAddress.getAllByName(InetAddress.java:1019)
at java.net.InetAddress.getByName(InetAddress.java:969)

Vilket tyder på att någon javaklass som inte är med på Googles lista över tillåtna klasser användes.  När jag studerade stacktracet lite närmare märkte jag att det var open-uri som ville använda javaklassen InetAddress som inte finns med i listan på godkända klasser.

Eftersom man med hjälp av Jruby kan ”scripta” java var det relativt enkelt att byta ut open-uri mot godkända javaklasser istället  och sedan automagiskt göra om java InputStream objektet till ett ruby io objekt som Hpricot kan ta i sin konstruktor. Lösningen blev enligt nedan.

require 'rubygems'
require 'hpricot'
# Importerar java istället för open-uri
require 'java'
# Skapa en instans av java-klassen URL
url = java.net.URL.new("http://athega.se")
# Kasta om java inputstreamen till ett ruby io objekt
ruby_io = org.jruby.RubyIO.new(JRuby.runtime, url.openStream)
io = Java.java_to_ruby(ruby_io.java_object)
# Sen är det bara att använda Hpricot som vanligt
doc = Hpricot(io)
result = doc/"//*[@id='helplist']/li/a"

Sedan transformerade jag resultatet till JSON och la upp applikationen här http://athega-news-api.appspot.com (OBS, applikationen returnerar JSON direkt så jag rekommenderar JSONView pluginet till Firefox om man vill titta på datan)

Om någon vill titta närmare på koden ligger den på Github men tänk på se till så att ni har tillstånd av rättighetsinnehavaren innan ni plockar data från webben.

HTML5 och canvas

Rita, animera och kontrollera pixlarna på webbsidan. Den nya <canvas>-taggen i HTML5 revolutionerar det som är möjligt att göra i en webbapplikation eller webbsida.

Det bubblar av exempel på och demonstrationer av vad som är möjligt med webbläsare som har stöd för HTML5. I skrivandets stund kan man slarvigt säga att det är de flesta större alternativen till Internet Explorer som visar framfötterna. Firefox, Safari och Opera är alla långt framme vad gäller stöd för HTML5. Microsoft är inte riktigt med i matchen ännu. men jobbar på det, i alla fall enligt egen utsago. Tyvärr är det ju så att IE måste med, för att vi ska kunna börja använda oss av HTML5-specifika funktioner på riktigt.

Den populära spegeleffekten

Spegeleffekt med canvas-taggen

Vi ska titta på hur man kan skapa en spegeleffekt av exempelvis en bild med hjälp av canvas-taggen. Jag har använt två canvas-taggar, en för huvudbilden och en för den reflekterande ytan. Huvudbilden hade kunnat visas med en vanlig img-tagg, men jag ville ha möjligheten att ”rita” och animera bilden också. Bilden föreställer min yngsta son, Carl.

Principen är enkel; skapa en canvas-tagg med vanlig markup, rita i den med JavaScript.

1. Skapa canvas-taggar

Börja med att definiera två canvas att rita i.

<canvas id="myCanvas" width="164" height="258">
  <p>Din webbläsare har inte stöd för canvas-taggen</p>
</canvas><br/>
<canvas id="mirrorCanvas" width="164" height="258"></canvas>

Innehållet i canvas-taggen är valfritt, men är tänkt att visas för äldre webbläsare eller webbläsare som helt enkelt inte har stöd för canvas-taggen (Varning: tidiga versioner av Safari visade dock detta innehåll). För att enkelt komma åt canvasytorna ger vi dem varsitt ID och en definierad storlek.

2. Ladda huvudbilden

var mainCanvas = document.getElementById('myCanvas');
var mainCtx = mainCanvas.getContext('2d');
 
var img = new Image();
img.src = 'carl.jpg';
img.onload = function() {
  mainCtx.drawImage(img, 0, 0);
};

För att komma åt pixlarna i canvas-ytan, hämtas ett 2d-kontext, som sedan används av drawImage för att rita bilden.

3. Skapa spegeleffekten

var mirrorCanvas = document.getElementById('mirrorCanvas');
var mirrorCtx = mirrorCanvas.getContext('2d');
 
var mainData = mainCtx.getImageData(0, 0, mainCanvas.width, mainCanvas.height);
var mirrorData = mirrorCtx.getImageData(0, 0, mirrorCanvas.width, mirrorCanvas.height);
 
var gradientStep = 70 / mainData.height;
for (var x = 0; x < mainData.width; x++) {
	var currentAlpha = 0;
	for (var y = 0; y < mainData.height ; y++) {
		var mainIdx = (x + y * mainData.width) * 4;
		var mirrorIdx = (x + (mainData.height - 1 - y) * mainData.width) * 4;
		for (p=0; p<3; p++) {
			mirrorData.data[mirrorIdx+p] = mainData.data[mainIdx+p];
		}
		mirrorData.data[mirrorIdx + 3] = currentAlpha;
 
		currentAlpha += gradientStep;
	}
}
 
mirrorCtx.putImageData(mirrorData, 0, 0);

Vi börjar med att hämta pixelarrayer för både huvudbilden och den spegelvända bilden. Detta görs med funktionen getImageData. Den returnerar en platt array med fyra poster för varje pixel. Dessa är i turordning värden för pixelns röda-, gröna-, blåa- och alphavärden.

Huvudbildens pixlar stegas sedan igenom kolumnvis, så att vi kan skapa en reflektion genom att gradvis öka värdet på alphakanalen från 0 till 70 (255 är max). Jag läser uppifrån och ner i huvudbilden, men ritar nerifrån och upp i spegelbilden och får på så sätt bilden spegelvänd.

Till sist uppdaterar vi spegelytan med den modifierade pixelarrayen genom att anropa putImageData.

4. Ett fungerande exempel

Här finns ett fungerade exempel. För att visa att spegelytan är levande, har jag lagt till en animering som startas och stoppas genom att klicka i bilden. Tänk på att du behöver en hyfsat uppdaterad webbläsare som inte är Internet Explorer.

HTML och JavaScript:

Avslutningsvis, några imponerande canvas-exempel

Realtidsrendrering av 3D-modell av en iPod Touch

Realtidsrendrering av 3D-modell av en iPod Touch

Ett fullt fungerade Asteroids implementerat endast med Canvas och JavaScript

Ett fullt fungerade Asteroids implementerat endast med Canvas och JavaScript

Ett canvas tillsammans med bland annat video-taggen för att analysera var de två iPhone-telfonerna är och i realtid uppdatera innehållet mellan dem med exempelvis innehållet från en annan video-tagg.

Ett canvas tillsammans med bland annat video-taggen för att analysera var de två iPhone-telfonerna är och i realtid uppdatera innehållet mellan dem med exempelvis innehållet från en annan video-tagg.

Läs mer

MongoDB

MongoDB - Scalability/Performance - Depth of Functionality

MongoDB är en blandning av nyckel-värde databas (t.ex. Scalaris), dokumentdatabas (t.ex. CouchDB) och relationsdatabas (t.ex. MySQL).

Den är skriven i C++ och passar bra för realtidsanalys av webbstatistik
(sidvisningar, unika besökare, osv), Sessionsobjekt, Datacachning och mycket mer.

Den har inbyggd replikering, automatisk failover och sharding, hög prestanda och den är open source.

Internt så sparas datan i formatet BSON, vilket är JSON med ett datumformat och möjlighet att spara binärdata.

Installation

Jag kommer som vanligt att använda Mac OS X och Ruby men det finns förkompilerade paket för de vanligaste operativsystemen:

  • OS X 32 bit
  • OS X 64 bit
  • Linux 32 bit
  • Linux 64 bit
  • Windows 32 bit
  • Solaris i86pc (EXPERIMENTAL)

Det finns även bindningar till ett flertal olika språk, dock har jag inte sett någon för .NET

Installationen var helt smärtfri, bara att ladda ner senaste tarbollen (mongodb-osx-x86_64-0.9.5) och packa upp den på valfri plats.

Man behöver skapa en datakatalog (standard är /data/db) och sen starta servern med

./mongod --dbpath [DATAKATALOGEN]

Nu lyssnar databasen på port 27017 och det finns ett webbgränssnitt på port 28017.

På Rubysidan behövde jag bara installera paketet mongodb-mongo från GitHub med RubyGems:

sudo gem install mongodb-mongo

Dags för lite kod

Att ansluta till databasen:

#!/usr/bin/env ruby
 
require 'rubygems'
require 'mongo'
 
db = XGen::Mongo::Driver::Mongo.new.db("mydb")

MongoDB har konceptet collections för likartade dokument. (Eller olikartade.. det bestämmer du helt själv) Man kan likna dem vid tabeller.

# Hämtar en collection, den skapas om den inte redan finns
coll = db.collection("testCollection")
 
# Man kan stoppa in olika sorters dokument
coll.insert({ :name => "Peter", :address => { :city => "Stockholm" } })
coll.insert({ :name => "Velociraptor", :top_speed => "25 m/s",
              :can => {
                :open_doors => true,
                :turn_at_any_angle_at_any_velocity => true
              }
            })
 
# Man kan sedan hämta ut datat ur databasen med metoden find_first.
peter  = coll.find_first(:name => 'Peter')
raptor = coll.find_first(:top_speed => '25 m/s')
 
# Man kan även hämta alla dokument i en collection
coll.find.each do |doc|
  puts doc.inspect
end

Vidare läsning

Jag har bara skrapat lite lätt på ytan av allt som går att göra med MongoDB, om du tycker att det verkar intressant så rekomenderar jag följande länkar:

Lär dig leva utan ancestorView

Under våren har jag jobbat med att trimma en webbapplikation som levererar nätupplagan av en av Sveriges större tidningar. CMS-verktyget som används är Escenic i version 4.x och det kommer med en rad egna JSP-tagar. En av dom ska man dock akta sig för – <section:ancestorView>. Den är faktiskt inte så svår att leva utan heller. I den här artikeln visar jag hur.

Responstid före och efter jag bytt ut <section:ancerstorView>

Responstid före och efter jag bytt ut ancerstorview-taggen

Vad är ancestorView?

ancestorView används för att skapa en hierarkisk vy av sektioner utgående från den sektion du anger. Inget speciellt avancerat eller konstigt. Det borde inte heller vara jobbigt för systemet att skapa vyn, tyvärr sker något under ytan som ställer till det. Berätta gärna vad i kommentarerna. ;)

Hur det kan se ut (före)

Ett vanligt sätt att använda sig av ancestorView är för att exempelvis bygga en sökväg till en navigering:

<section:ancestorView id="sectionView" section="${article.homeSection}" includeRoot="true"/>
<menu:use id="navigation" treeName="myMenuName">
	<c:set var="count" value="1"/>
	<view:iterate id="item" name="sectionView">
		<menu:item id="current" sectionId="${item.id}"/>
		<c:choose>
			<c:when test="${count eq 1}">
				<c:set var="sectionPath" value="${current.text}"/>
			</c:when>
			<c:otherwise>
				<c:set var="sectionPath" value="${sectionPath}/${current.text}"/>
			</c:otherwise>
		</c:choose>
		<c:set var="count" value="${count+1}"/>
	</view:iterate>
</menu:use>

Skapa en lättviktig ersättning till ancestorView

En enkel väg till ett liv utan ancestorView-taggar är att skapa en mycket enkel custom tag. Nedanstående implementation har inte stöd för precis allt du kan göra med Escenics variant, men den gör jobbet för de flesta användningsfallen. Jag döpte den till ancestors.tag och la den i /WEB-INF/lib/tags/section.

<%@tag body-content="empty"%>
<%@tag import="neo.xredsys.api.Section, java.util.ArrayList"%>
<%@attribute name="id" required="true" rtexprvalue="false"%>
<%@attribute name="section" type="neo.xredsys.api.Section" required="true"%>
<%@attribute name="includeRoot" type="java.lang.String" required="false"%>
<%@variable name-from-attribute="id" alias="sectionPath" scope="AT_END"%>
<%
	final ArrayList sections = new ArrayList();
	if (section != null) {
		do {
			sections.add(0, section);
			section = section.getParent();
		} while (section != null && ("true".equals(includeRoot) || section.getParent() != null));
	}
	jspContext.setAttribute("sectionPath", sections);
%>

Det enda som egentligen sker här är att jag bygger upp en lista med föräldrasektioner i omvänd ordning genom att anropa Escenics API. Detta går av någon anledning massor med gånger snabbare.

Hur det kan se ut utan ancestorView
<%@ taglib prefix="sec" tagdir="/WEB-INF/tags/section" %>
<sec:ancestors id="sectionView" section="${article.homeSection}"/>
<menu:use id="navigation" treeName="myMenuName">
	<c:forEach var="item" items="${sectionView}" varStatus="itemStat">
		<menu:item id="current" sectionId="${item.id}"/>
		<c:choose>
			<c:when test="${itemStat.first}">
				<c:set var="sectionPath" value="${current.text}"/>
			</c:when>
			<c:otherwise>
				<c:set var="sectionPath" value="${sectionPath}/${current.text}"/>
			</c:otherwise>
		</c:choose>
	</c:forEach>
</menu:use>

Eftersom jag inte längre arbetar med en vy av sektioner, kan jag iterera över listan med föräldrasektioner med en vanlig c:foreach med fördelar som varStatus, mm.

Slutsats

Om du använder Escenic i någon 4.x-version och om du får några träffar när du söker på ancestorView i din kodbas, finns all anledning att se över ett byte!