Inlägg taggade ‘Prestanda’

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

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!

RailsConf: Onsdag

Keynote: Chris Wanstrath

Presentationen: https://gist.github.com/0a2655aed6a26fa15a02

Rails Metal, Rack, and Sinatra

Adam Wiggins (Heroku) berättade om hur Rails Metal går att kombinera med Sinatra.

What Makes Ruby Go: An Implementation Primer

Charles Nutter (Sun Microsystems) och Evan Phoenix (Engine Yard) gick igenom olika delar av Ruby som man behöver tänka på för att inte stöta på prestandaproblem.

Metodanrop

Att cache:a metodanrop ger generellt sett den största prestandavinsten.

Object#extend är dock ett stort problem för att kunna cache:a metodanropen.

Det är viktigt att förstå hur extend fungerar så att man inte tömmer
metodanropscachen i onödan.

Konstanter

För att hålla prestandan upp ska man hålla konstanter konstanta, inte helt oväntat.

Options Argument

En option hash strider mot DRY.
Det är mycket snabbare att använda diskreta argument.

obj.run rescue nil

Det är otroligt ovanligt att man vill fånga alla StandardError, vilket är 101 underklasser; IOError, SecurityError, TypeError, Etc.

obj.run rescue Exception

I stort sett samma sak som rescue nil, fast värre, nu kan man inte ens ctrl-C’a ut ur koden.

Autoload

Helt tråd-osäkert, fördröjer laddning av koden.
Använder inte Kernel#require, alltså går det inte att köra autoload från gems.

Super

Man måste ta bort blocket om man inte vill att det ska skickas uppåt:

super(a, &nil)

Super ser bara senaste versionen av argumenten:

def foo(a, b)
  a = 1
  b = 'bar'
  super
end

Slutsats

  • Enkel kod > Komplex kod
  • Det finns inga gratis luncher
  • Tänk igenom två gånger, skriv koden en gång
  • YAGNI

Call into your Ruby code! Writing voice-enabled apps in Ruby with Adhearsion

Jay Phillips (Codemecca LLC) visade hur man kan programmera Ruby för att styra Asterisk genom att använda sig av Adhearsion.

Tyvärr fungerade det inte att använda Ahearsions Sandbox på konferensens wlan,
så jag får ta och testa det lite senare.

Verkar intressant att kunna styra sin applikation genom att ringa till den :)

Låt inte Tomcat jobba i onödan

Ett vanligt upplägg för en lastad sajt med någorlunda statiskt innehåll ser ut enligt nedan:

Upplägg, lastad sajt

Upplägg, lastad sajt

I korthet innebär det att den största delen av trafiken hanteras av enkla webbcachear som leverar sidor till slutanvändarna. Med ett visst intervall efterfrågar cachearna frontarna efter nytt innehåll. Frontarna kan exempelvis köra en eller flera Tomcat-instanser som genererar innehållet. På detta sätt kan även riktigt stora siter klara sig på ett tiotal maskiner.

För alla JSP-sidor som Tomcat-frontarna genererar åt cachearna skapas nu en session, precis som Servlet-standarden föreskriver. Det är helt i sin ordning, fast helt onödigt i det här fallet. Det genererade innehållet levereras inte direkt till slutanvändaren, utan till cachen. Kan man få servlet-motorn att inte skapa sessioner i onödan och på så sätt spara sina dyra resurser?

Ja, ett sätt är att överst i sin JSP-sida helt enkelt utbrista:

<%@page session="false"%>

Dock är det kanske inte att föredra i ett litet större projekt med hundratals JSP-sidor. Det man letar efter är ett sätt att slå av det centralt för alla JSP-sidor. Min första instikt var att konfiguerar Jasper, som kompilerar JSP-sidorna att inte skapa sessioner om man inte explicit bad om det. Någon sådan parameter hittade jag tyvärr inte. Personer som har liknande frågor på nätet får istället frågan; ”Varför vill du slå av sessioner? De är ju en del av standarden!”

Mitt nästa spår var att automatiskt försöka inkludera ovanstående direktiv för varje genererad sida. Från och med JSP 2.0 (som inte direkt kom ut igår) går det att få till ganska enkelt med något som kallas för implicit includes.

Börja med att lägga till ett jsp-config-direktiv i din web.xml:

<!-- Disable the use of sessions -->
<jsp-config>
  <jsp-property-group>
    <url-pattern>*.jsp</url-pattern>
    <include-prelude>/WEB-INF/jspf/disableSession.jspf</include-prelude>
  </jsp-property-group>
</jsp-config>

Detta matchar alla JSP-filer (med filändelsen .jsp) och inkluderar innehållet av filen disableSession.jspf precis som om du skulle ha skrivit

<%@include file="/WEB-INF/jspf/disableSession.jspf"%>

högst upp i varenda JSP-fil.

Notera att filen som inkluderas är av typen .jspf, ett JSP-fragment. Detta är nödvändigt och säkerställer bland annat att du inte råkar in i några oändliga inkluderingsloopar. Att filen ligger i mappen /WEB-INF/jspf är rekommenderat och förhindrar även direkt access för en slutanvändare.

Testa! Tomcat kommer att kasta fel i loggarna så fort du försöker skapa en session, men om du har det som jag är det precis så du vill ha det!