субота, 28. новембар 2009.

elif евалуација

Интересантна промена се десила у gcc Ц препроцесору. Евалуација elif гране препроцесорске директиве "if" се извршава чак и ако је услов дат у if задовољен.

Конкретан пример:

#ifdef
#ifndef FOO_BAR
#define FOO_BAR
#elif FOO_BAR == 0
#endif

Ово ради без проблема на претходним верзијама, на пример 4.3.3 али не и на 4.4.1. Не знам у којој тачно верзији је дошло до промене, али ево извештаја проблема: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36320

Исправна конструкција горе наведеног примера би била:


#ifdef
#ifndef FOO_BAR
#define FOO_BAR 1
#elif FOO_BAR == 0
#endif

петак, 27. новембар 2009.

Секвентна тачка

Односно "Sequence point".

Питање у вези секвентне тачке (ако сам превео добро) често долази на разговорима за посао. Многи нису свесни шта је то иако је у суштини проста ствар. Пре неки дан ме на то потсетио бивши колега, а и ја сам то питање добио на мом разговору.

Секвентна тачка је синтаксни елемент дефинисан језиком који гарантује да ће споредни ефекти евалуације израза са леве стране таквог елемента гарантовано бити завршени пре него што започне евалуација израза са десне стране.

На пример, у Цеу и Це++ то је оператор зарез ',' али не и аримтетички оператори. Осим зареза, има још секвентних тачака као што су '?' и логички оператори.

Кратак, али добар текст се може наћи на википедији.

уторак, 27. октобар 2009.

Решавање судоку

Прошле године су нам у посети били моји родитељи и при том сам сазнао да је мој отац постао пасионирани играч судоку. Мало сам играо и ја, врло интересантна мозгалица. Природно, дошао сам на идеју да решавање "аутоматизујем". У то време сам учио "Scheme" језик - шема ме још занима али се нема времена.

Ипак, склепао сам некакав програмчић а ових дана га пречистио и проверио. Програм следи у целини.

Програм је развијан и тестиран коришћењем "Petite Chez Scheme" http://www.scheme.com/petitechezscheme.html.


; Решавање судоку матрице.
; Функција за решавање је:
; (solve sudo azbuka)
; где је sudo низ који описује почетно
; стање судоку, а azbuka низ који дефинише
; могуће елементе. Класичан судоку ће
; у азбуци имати цифре 1 до 9.


;
; Дефиниција примера судоку проблема
; Судоку таблица се задаје као низ
; по редовима.
;
; Редови и колоне су нумерисане
; бројевима 1 до 9.
;
; Непознати елементи су задати елементом
; који није садржан у азбуци. У примеру
; који следи, елементе које сам хтео да
; прогласим непознатим сам обележио подвлаком.
;
; Резултат обележава решене елементе
; заградом (под листа). Уколико
; једнозначно решење није нађено,
; у загради ће бити изслитана могућа
; решења.
;
(define sudo '(  _1  2 _3  4  5  6 _7 _8 _9
                  4 _5  6  7 _8 _9  1  2  3
                 _7 _8 _9 _1  2  3 _4 _5  6
                  2  3 _4  5  6  7 _8 _9  1
                 _5  6  7 _8 _9  1  2  3  4
                 _8 _9  1  2  3  4  5  6  7
                 _3  4  5  6 _7 _8 _9  1  2
                 _6  7 _8 _9  1  2  3  4  5
                 _9 _1  2  3 _4  5  6 _7  8   ))


;
; Азбука.
; Азбука може бити произвољна све док задовољава
; услов да је број елемената једнак квадрату
; целобројног позитивног броја.
;       
(define azbuka '(1 2 3 4 5 6 7 8 9))

(define a2 '(0 1 2 3))
(define s2 '( ? 1 2 3
              3 2 1 ?
              1 3 0 2
              2 0 ? 1))
              


;
; Основна функција за решавање судоку.
;
(define (solve sudo azbuka)
    (solve1 sudo azbuka 0))

; 
; Помоћна функција за решавање
;
(define (solve1 sudo azbuka rc)
    (cond
   ((>= rc (* (list-len azbuka) (list-len azbuka))) sudo)
      (else
        (solve1
          (append 
            (front (reduce sudo azbuka) rc)
            (cons
              (possible (reduce sudo azbuka) azbuka (row-idx azbuka rc) (col-idx azbuka rc))
              (skip-n (reduce sudo azbuka) (1+ rc))))
          azbuka
          (1+ rc)))))



;
; Редукуј све непознате чланове на листе могућих
; решења
;
(define (reduce sudo azbuka)
 (reduce1 sudo azbuka 0))

;
; Помоћна функција.
; Редукуј све непознате чланове на листе могућих
;
(define (reduce1 sudo azbuka rc)
 (cond
  ((> (1+ rc) (* (list-len azbuka) (list-len azbuka))) '())
  (else
   (cons
    (possible sudo azbuka (row-idx azbuka rc) (col-idx azbuka rc))
    (reduce1 sudo azbuka (1+ rc))))))


;
; Помоћна функција - број елемената у 
; датој листи.
;
(define (list-len lst)
    (cond
      ((or (null? lst) (is-atom? lst)) 0)
      (else
        (1+ (list-len (cdr lst))))))
        
; 
; Помоћна функција - израчунава димензију под-судоку
; матрице.
;
(define (koren azbuka) (sqrt (list-len azbuka)))
                                                                     
;                                                                    
; Одређивање природе вредности aorl
; Враћа #f уколико дата вредност није
; атом.
;
(define is-atom? atom?)

;(define (is-atom? aorl)
;    (cond
;      ((or 
;           (list? aorl)
;           (not (number? aorl))) #f)
;      (else (atom? aorl))))

; 
; Да ли је дати елемент слово азбуке
;
(define (member? at lst)
 (cond
  ((null? lst) #f)
  (else
   (or (eq? at (car lst))
    (member? at (cdr lst))))))

;
; Да ли је дата вредност задата?
;
(define (is-given? aorl azbuka)
    (and (is-atom? aorl) (member? aorl azbuka)))

;
; Да ли је вредност решена?
;
; Решена је само онда када је представљена
; листом са тачно једним елементом који
; није 0.
;
(define (is-solved? lat azbuka)
    (cond
      ((eq? 1 (list-len lat)) #t)
      (else (is-given? lat azbuka))))

;
; Помоћна функција - број елемената у 
; датој листи.
;
(define (list-len lst)
    (cond
      ((or (null? lst) (is-atom? lst)) 0)
      (else
        (1+ (list-len (cdr lst))))))

;
; Прескочи n елемената и врати
; n-ти елемент.
;
(define (skip-n sudo n)
    (cond
      ((null? sudo) '())
      ((eq? 0 n) sudo)
      (else
        (skip-n (cdr sudo) (1- n)))))

;
; Врати листу од n елемената почев од
; датог елемента sudo
;
(define (front sudo n)
    (cond
      ((null? sudo) '())
      ((eq? 0 n) '())
      (else
        (cons (car sudo) (front (cdr sudo) (1- n))))))

;
; Издвој ред n
; 
(define (row sudo azbuka n)
    (cond
      ((null? sudo) '())
      (else
        (front (skip-n sudo (* (1- n) (list-len azbuka))) (list-len azbuka)))))

;
; Помоћна функција за издвајање колоне
;
(define (getcol sudo azbuka n)
    (cond
      ((null? sudo) '())
      ((eq? 0 n) '())
      (else
        (cons (car sudo) (getcol (skip-n sudo (list-len azbuka)) azbuka (1- n))))))

;
; Издвоји колону n
;
(define (col sudo azbuka n)
    (cond
      ((null? sudo) '())
      (else
        (getcol (skip-n sudo (1- n)) azbuka (list-len azbuka)))))
       
;
; помоћна функција - врати ред (почев од 1)
; на основу датог индекса листе која дефинише
; судоку. Индекс почиње са 0.
; 
(define (row-idx azbuka idx)
 (1+ (floor (/ idx (list-len azbuka)))))
 
;
; помоћна функција - врати колону (почев од 1)
; на основу датог индекса листе која дефинише
; судоку. Индекс почиње са 0.
; 
(define (col-idx azbuka idx)
 (1+ (- idx (* (1- (row-idx azbuka idx)) (list-len azbuka)))))

;
; Помоћна функција за издвајање "мини судоку" односно
; подматрице
;
(define (sub-sudo-row sudo azbuka r c rn koren)
 (cond 
  ((> rn koren) '())
  ((null? sudo) '())
 (else
  (append
   (front (skip-n (row sudo azbuka (+ rn (* (1- r) koren))) (* (1- c) koren)) koren)
   (sub-sudo-row sudo azbuka r c (1+ rn) koren)))))

;
; Издвој мини судоку (a пута a, где је а корен
; броја елемената у азбуци) са задате позиције.
; Позиције мини судоку су нумерисане 1 до (list-len azbuka).
;
(define (sub-sudo sudo azbuka r c)
 (sub-sudo-row sudo azbuka r c 1 (koren azbuka)))

;
; 
;
(define (extract-solved su azbuka)
    (cond
      ((null? su) '())
      ((is-solved? (car su) azbuka) 
    (cons 
  (cond
    ((list? (car su)) (caar su))
    (else
    (car su)))
    (extract-solved (cdr su) azbuka)))
      (else
        (extract-solved (cdr su) azbuka))))

;
; Разлика две судоку
;
(define (diff su lat)
    (cond
      ((null? su) '())
      ((null? lat) su)
      ((member? (car su) lat) (diff (cdr su) lat))
      (else
        (cons (car su) (diff (cdr su) lat)))))

;
; Да ли је вредност at члан низа lat?
;
(define (member? at lat)
    (cond
      ((null? lat) #f)
      ((eq? at (car lat)) #t)
      (else
        (member? at (cdr lat)))))

;
; Врати сва немогућа решења за дату позицију.
; (сви елементи азбуке који су већ дати или
; нађени у датом реду, колони и под судоку).
;
(define (allsolved sudo azbuka r c)
    (append
      (extract-solved (row sudo azbuka r) azbuka)
      (extract-solved (col sudo azbuka c) azbuka)
      (extract-solved 
  (sub-sudo 
   sudo azbuka (ceiling (/ r (koren azbuka))) (ceiling (/ c (koren azbuka))))
  azbuka)))


;
; Врати листу могућих решења за дати елемент.
; 
(define (possible sudo azbuka r c)
    (cond
      ((is-solved? (element sudo azbuka r c) azbuka)
       (element sudo azbuka r c))
      (else
        (diff azbuka (allsolved sudo azbuka r c)))))

;
; Помоћна функција. Врати дати елемент.
; Ред и колона почињу од 1
;
(define (element sudo azbuka r c)
    (car (skip-n sudo (+ (* (list-len azbuka) (1- r)) (1- c)))))


gcc и груписање функција

У неким ретким случајевима, потребно је оптимизовати програм тако да се смањи растојање између функција које се често позивају. Могући добитак од овакве оптимизације је смањење "TLB misses" односно потребе да процесор учитава податке за превођење виртуелне у физичку адресу за дати блок. Друга могућа добит је искоришћење различитих нивоа кеширања: уколико су функције физички близу, велика је вероватноћа да ће, док се прва извршава, друга бити учитана у кеш.

Конкретан пример који имам је оптимизација кода за "лењо разрешавање" адреса симбола у динамички повезаним програмима (lazy binding).

Наш оперативни систем (QNX Neutrino) има динамичко повезивање, али не "уме" да то уради "лењо" односно на захтев. Како сада ради, наш динамички повезивач разрешава све адресе симбола на самом почетку, пре него што програм стигне у "main". Последњих неколико дана сам радио на имплементацији која већ постоји на већини јуникс система, а која подразумева разрешавање симбола функција у тренутку првог позива.

Како између два разрешења може доста кода да се изврши, велика је вероватноћа да ће функције потребне за лењо разрешење већ бити избачене из кеша, а такође из процесорске табеле за превођење адреса (TLB) избачене ставке које се односе на физичке странице меморије у којима су те функције. Груписањем функција тако да буду близу једна другој, идеално у једној физичкој страници меморије, ће смањити број промашаја.

Приликом прве компилације установио сам да су функције на приличном растојању једна од друге. Конкретно, приликом дисасемблирања објектног фајла, добио сам ово:

bind_func     0x000000cc
 resolve_func  0x000029e0
 resolve_rels  0x00001010
 lookup_global 0x00000c68

Види се да би фунције биле распоређене у бар 3 меморијске странице (под условом да је страница 4KiB).

Да бих груписао исте, искористио сам атрибут фунције "hot" уведен са gcc верзијом 4.3. Горе наведеним функцијама сам у декларацију додао: __attribute__((hot)). Gcc тако означене функције оптимизује агресивније али и групише у посебну секцију ".text.hot". На крају процеса повезивања, функције су распоређене једна до друге:

bind_func    0x0003f3f8
 resolve_rels 0x0003f418
 resolve_func 0x0003f88c

Интересантно је да је lookup_global "нестала" односно апсорбована у функције које је позивају.

У сваком случају, уместо 4 физичке странице сада функције могу да стану у само једну и распоређене су једна уз другу чиме се повећава шанса да у тренутку позива већ буду у кешу.


Тек треба да упоредим да ли се овиме добија нешто што може да се измери. Вероватно ће доста зависити од процесора па и самог уређаја.

субота, 24. октобар 2009.

7-zip

Коначно сам инсталирао 7-zip (http://www.7-zip.org/) и код куће. Већ неколико месеци га користим на послу. 7-zip "разуме" различите формате архива, између осталих .zip, .rar, .bz2...

WinZip ми стварно неће недостајати.

уторак, 29. септембар 2009.

"Безбедне" Ц функције за манипулацију текста

Често се нађем у ситуацији да нисам сигуран да ли одређена ц функција за манипулацију текста (snprintf, strncpy, strncat, ...) исписује тачно "n" карактера, да ли резултирајући стринг завршава нулом итд.

Ради лакшег потсећања, ево примера за сваку од функција:


#include <stdio.h>
#include <string.h>

static void koristi_snprintf(const char *const tekst)
{
    char buf[10];

    snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s", tekst);
    /* Текст који смо добили у buf је можда скраћен, али је коректан са
     * нулом на крају. */
    printf("%s\n", buf);
}


static void koristi_strncpy(const char *const iz)
{
    char u[10];
    const unsigned s = sizeof(u)/sizeof(u[0]);

    strncpy(u, iz, s - 1);
    u[s - 1] = '\0'; /* Морамо ручно да завршимо стринг. */

    printf("%s\n", u);
}


static void koristi_strncat(const char *const tekst)
{
    char buf[10];
    const unsigned s = sizeof(buf)/sizeof(buf[0]);

    strncpy(buf, "12345", s - 1);
    buf[s - 1] = '\0';

    /* Број карактера који функција може да дода је мања за један
     * због завршне нуле и мања за дужину стринга који је већ у
     * баферу. */
    strncat(buf, tekst, s - 1 - strlen(buf));
    printf("%s\n", buf);
}


static void koristi_strncmp(const char *const tekst)
{
    const char buf[] = "12345";
    const unsigned s = sizeof(buf)/sizeof(buf[0]);

    if (strncmp(buf, tekst, s) == 0)
 printf("Prvih %d karaktera su isti\n", s);
    else
 printf("Razlika postoji u prvih %d karaktera\n", s);
}



int main()
{
    koristi_snprintf("Zdravo. Ovaj tekst je predugacak.");
    koristi_strncpy("Zdravo. Ovaj tekst je predugacak.");
    koristi_strncat("Zdravo. Ovaj tekst je predugacak.");
    koristi_strncmp("12345678");
    return 0;
}

четвртак, 28. мај 2009.

Условна дијагностика у јави

Постоји неколико широко распрострањених библиотека за исписивање дијагностичких порука (енгл. logging). На пример, java.util.logging.

У својим програмима користим ово, али ми недостаје условна компилација. Оно што у Ц/Ц++ могу да урадим са #ifdef  . . . не могу у јави. Тако сам приморан да дијагностику коју користим искључиво за време развоја, и не желим да буде присутна у крајњем производу морам да пишем овако: 



void foo(int bar) {
if (DEBUG)
logger.trace("U foo(" + bar + ")");
}


Међутим, са јавом 1.5, дошла је и кључна реч assert. Ово је добродошло јер омогућава убацивање провера у сам код, а да то не ремети перформансе крајњег производа. Као посебан бонус, који није доступан у Ц/Ц++, асерти се могу укључити по потреби једноставним давањем аргумента -ea јавиној виртуелној машини.

То ме је подстакло да направим једноставан систем за условну дијагностику сличан #ifdef приступу у Ц/Ц++. Користим постојеће пакете (на пример java.util.logging) али унутар асерта. Наравно, ово није могуће урадити директно јер, на жалост, интерфејси дефинисани у овим пакетима не враћају никакву вредност. Мени је било потребно да сваки метод враћа вредност true да бих исти могао да користим унутар assert исказа:



void foo(int bar) {
assert logger.trace("U foo(" + bar + ")");
}


Ово ми даје управо оно што сам желео: условну дијагностику која је у нормалном режиму рада потпуно избачена оптимизацијом. То је чак и боље од Ц/Ц++ условне компилације јер се са јавом коначна компилација врши на рачунару корисника: уколико је јава виртуелна машина стартована са опцијом -ea онда ће дијагностика бити исписивана, у супротном не.

Моја имплементација се састоји у дефинисању интерфејса:



/**
* Copyright (c) 2007 Aleksandar Ristovski.
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Aleksandar Ristovski - initial API and implementation
*/
package org.java3declipse.logger;

/** ILogger interface.
*
* This interface is very similar to existing logger
* interfaces, with one significant difference: the
* implementation should always return true.
*
* <p> <b>Rationale:</b>
*
* <p> Logging is a nice feature but can have performance
* impact. If we simply call logger's log method, even if
* the logging level is lower than stated, arguments get
* evaluated. We typically have string concatenations in our
* log message which can have significant impact on memory
* fragmentation and speed.
*
* <p> Some approaches use conditional which is much better
* performance-wise. I don't like this approach as the
* statement is still there. <p> This approach suggests
* using <code>assert</code>. When program is started with
* '-ea', logging is 'on'. When program is started without
* assertions enabled, the logging statements are completely
* optimized out. <p> Typical example:
*
* <pre>
* import org.java3declipse.logger.ILogger;
* import org.java3declipse.logger.Logger;
*
* public class MyClass {
* private static ILogger logger =
* Logger.getLogger(MyClass.class.getSimpleName());
*
* public MyClass() {
* assert logger.info("MyClass constructor");
* }
* }
* </pre>
*
*
* @author Aleksandar Ristovski
*/
public interface ILogger {
boolean info(String msg);
boolean warning(String msg);
boolean error(String msg);
boolean error(String msg, Throwable t);
boolean debug(String msg);
boolean debug(String msg, Throwable t);
}


И имплементацији истог:



/**
* Copyright (c) 2007 Aleksandar Ristovski.
* All rights reserved. This program and the accompanying
* materials are made available under the terms of the
* Eclipse Public License v1.0 which accompanies this
* distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Aleksandar Ristovski - initial API and implementation
*/
package org.java3declipse.logger;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

/**
* Logger class - default implementation of ILogger interface.
*
* Wrapper for a generic logger. This class provides default
* implementation for ILogger interface.
*
* @see ILogger
* @author Aleksandar Ristovski
*
*/
public class Logger implements ILogger {
private ILogger logger_;

/** Constructor, using initialized
* <code>java.util.logging.Logger</code>.
*
* @param logger - initialized
* <code>java.util.logging.Logger</code>
*/
public Logger(java.util.logging.Logger logger) {
logger_ = this.new JavaUtilLogger(logger);
}

/** Constructor - using user implementation of ILogger.
*
* This constructor can be used if logger other than
* <code>java.util.logging.Logger</code> is desired.
*
* @param logger - initialized object that
* implements <code>ILogger</code> interface
*/
public Logger(ILogger logger) {
logger_ = logger;
}

public boolean info(String msg) {
return logger_.info(msg);
}

public boolean warning(String msg) {
return logger_.warning(msg);
}

public boolean error(String msg) {
return logger_.error(msg);
}

public boolean error(String msg, Throwable t) {
return logger_.error(msg, t);
}

public boolean debug(String msg) {
return logger_.debug(msg);
}

public boolean debug(String msg, Throwable t) {
return logger_.debug(msg, t);
}

private class JavaUtilLogger implements ILogger {
final java.util.logging.Logger javaUtilLogger;
public JavaUtilLogger(java.util.logging.Logger logger) {
javaUtilLogger = logger;
}
public boolean info(String msg) {
javaUtilLogger.info(msg);
return true;
}
public boolean warning(String msg) {
javaUtilLogger.warning(msg);
return true;
}
public boolean error(String msg) {
javaUtilLogger.severe(msg);
return true;
}
public boolean error(String msg, Throwable t) {
javaUtilLogger.log(Level.SEVERE, msg, t);
return true;
}
public boolean info(String string, Throwable e) {
javaUtilLogger.log(Level.INFO, string, e);
return true;
}
public boolean debug(String msg) {
javaUtilLogger.log(Level.FINE, msg);
return true;
}
public boolean debug(String msg, Throwable t) {
javaUtilLogger.log(Level.FINE, msg, t);
return true;
}

}

private static class SimpleFormatter extends Formatter {
@Override
public String format(LogRecord record) {
StringBuffer sb = new StringBuffer(80);
final String ln = record.getLoggerName();
final String msg = record.getMessage();
int level = record.getLevel().intValue();
if (level == Level.INFO.intValue())
sb.append("[INFO] ");
else if (level == Level.WARNING.intValue())
sb.append("[WARNING] ");
else if (level == Level.SEVERE.intValue())
sb.append("[SEVERE] ");
if (null != ln) {
sb.append(record.getLoggerName());
if (ln.length() + msg.length() > 75)
sb.append("\n");
else
sb.append("\t");
}
sb.append(record.getMessage());
sb.append("\n");
final Throwable thr = record.getThrown();
if (null != thr) {
ByteArrayOutputStream btbuff
= new ByteArrayOutputStream(80);
sb.append("Exception: ");
thr.printStackTrace(new PrintStream(btbuff));
sb.append(btbuff.toString());
sb.append("\n");
}
return sb.toString();
}
}

/** Convenience routine.
*
* Creates logger based on java.util.logging.Logger
* class.
*
* @param ID - logger id. Typically
* <code>Classname.class.getSimpleName()</code>
* @return logger
*/
public static ILogger getLogger(String ID) {
java.util.logging.Logger logger
= java.util.logging.Logger.getLogger(ID);
Handler h = new ConsoleHandler();
h.setFormatter(new SimpleFormatter());
logger.setUseParentHandlers(false);
logger.addHandler(h);
return new Logger(logger);
}
}