miércoles, 13 de febrero de 2013

Network Manager and NFS problem on Shutdown

I power up my personal computer with debian system installed. I login and automatically my network manager connect to predefined wireless network because the application nm-applet (network manager applet) or similar has send the order to network manager daemon using dbus communication. I mount one folder using NFS o CIFS.
When i shutdown my computer the error message () appear at the end of shutdown process.
This situation is because the network manager daemon is release before unmount network file systems. The idea is correct because the network manager daemon establish a connection when the nm-applet send the order and the nm-applet only run when user as logging.  But the nfs is a basic part of system like networking. The problem is that networkmanager is used to respond user request (at user level) and change basic system configuration like network parameters. The nfs depend of network interfaces and is release before the network is down but it refer to basic networking daemon, not to networkmanager.
The network manager daemon can modify network connection but must take in consideration that it can release the current connection on shutdown because is the networking daemon who must to do it.
networking - work at system level
network file system - work at system an user level.
networkmanager - work at user level but modifying networking ( problem because start late in time and change system configuration that must be defined at system boot)

The solution is change the release order of daemon on system level 6 and 0. 

ls /etc/rc6.d/
...
K02network-manager -> ../init.d/network-manager
K03sendsigs -> ../init.d/sendsigs
K04rsyslog -> ../init.d/rsyslog
K05umountnfs.sh -> ../init.d/umountnfs.sh
K06nfs-common -> ../init.d/nfs-common
K06rpcbind -> ../init.d/rpcbind
K07hwclock.sh -> ../init.d/hwclock.sh
K07networking -> ../init.d/networking
K08umountfs -> ../init.d/umountfs
K09umountroot -> ../init.d/umountroot
K10reboot -> ../init.d/reboot

ls /etc/rc0.d/

K02network-manager -> ../init.d/network-manager
K03sendsigs -> ../init.d/sendsigs
K04rsyslog -> ../init.d/rsyslog
K05umountnfs.sh -> ../init.d/umountnfs.sh
K06nfs-common -> ../init.d/nfs-common
K06rpcbind -> ../init.d/rpcbind
K07hwclock.sh -> ../init.d/hwclock.sh
K07networking -> ../init.d/networking
K08umountfs -> ../init.d/umountfs
K09umountroot -> ../init.d/umountroot
K10halt -> ../init.d/halt

Move network-manager from position 2 to position 7 at the same level that networking because it do the same that networking at release (deconfigure network interfaces).

martes, 15 de enero de 2013

Moving out Firefox's profile to ram filesystem

Firefox's profile dir

Hi, I need to optimize firefox execution getting somes files out of disk to ram and come back to disk to retaing data. In linux firefox use the folder $HOME/.mozilla/firefox to store all available profiles. I use this script to make it possible.

FF_BIN=/opt/32/firefox/firefox
PROFILE_NAME=$USER

# Launch firefox with profile in ramfs, wait and update modified files to backup

RAMFS=/ramfs
PROFILE_ROOT=$HOME/.mozilla/firefox   #firefox root directory
PROFILE_FILE=$PROFILE_ROOT/profiles.ini

#check preconditions
[ ! -e $PROFILE_FILE ] && echo "error: not file $PROFILE_FILE " >> /opt/bin.log && exit


#read all section when name is present hold. for next keys use name.key = value

while IFS="\=" read -r key val ; do
    [ -z $val ] && continue             # eval -zero string if true eval continue, the result is not use

    if [ $key == "Name" ] ; then
        PREFIX=$val
        continue
    fi

    [ -z $PREFIX ] && continue

    export "${PREFIX}_$key=$val"        #make visible out of while do not use dot because it is not valid for variable name 

done < $PROFILE_FILE 

# copy to ramfs only one time, remain until next reboot, get backup on every exit
eval PROFILE_DIR=\$\{${PROFILE_NAME}_Path\}
eval PROFILE_PATH=$PROFILE_ROOT/\$\{${PROFILE_NAME}_Path\}

[ -z $PROFILE_DIR ] && echo "error: profile $PROFILE_NAME not found in $PROFILE_FILE " >> /opt/bin.log && exit
[ ! -d ${PROFILE_PATH}.backup ] && echo "error: directory ${PROFILE_PATH}.backup not found " >> /opt/bin.log && exit

RAMFS_PROFILE_PATH=${RAMFS}$PROFILE_PATH

if [ ! -d $RAMFS_PROFILE_PATH ] ; then
    mkdir -p $RAMFS_PROFILE_PATH
    cp -axr ${PROFILE_PATH}.backup/. $RAMFS_PROFILE_PATH
    ln -sf $RAMFS_PROFILE_PATH  $PROFILE_ROOT
fi

$FF_BIN -p $PROFILE_NAME -no-remote  $@

#update modified files
if [ ! -z $RAMFS_PROFILE_PATH ] ; then
# -E to preserve exection flag.
rsync -avqE --delete-after $RAMFS_PROFILE_PATH/. ${PROFILE_PATH}.backup
fi

exit

   

Understanding Bash quoting

We use this simple bash file to validate all examples

#!/bin/bash
echo "Arguments count : $#"
echo "All : $@"

for (( i=0; i<=$#; i++ )); do
    eval arg=\${$i}
    echo $i : $arg
done

exit

Especial character # use to comment a line. See next.

./print-arg.sh arg1 arg2 arg3  #arg4 ignore 
Arguments count = 3
All : arg1 arg2 arg3
0 : arg1
1 : arg2
2 : arg3

To avoid bash interprets the character # we use quotes, simple or double do not matter

./print-arg.sh arg1 arg2 arg3  '#arg4' ignore 
Arguments count = 5
All : arg1 arg2 arg3 #arg4 ignore
0 : arg1
1 : arg2
2 : arg3
3 : #arg4
4 : ignore
./print-arg.sh arg1 arg2 arg3   "#arg4" ignore 
Arguments count = 5
All : arg1 arg2 arg3 #arg4 ignore
0 : arg1
1 : arg2
2 : arg3
3 : #arg4
4 : ignore

Bash interprets the blank space has argument separation, to avoid this we use simple or double quoted

./print-arg.sh "arg1 arg2" arg3    "#arg4 ignore" 
Arguments count : 3
All : arg1 arg2 arg3 #arg4 ignore
0 : ./print-arg.sh
1 : arg1 arg2
2 : arg3
3 : #arg4 ignore

To pass espaces use simple or double quoting, to pass quote as another character, use simple to double and double to simple. Use double if there is some spaces and use escape char \ to pass double quote bettewn them.
Some special chars in bash

  • * : use for expand files in current path
  • space : Use for arguments and elements separation
  • # : from this character to the end of the line text is a comment
  • $ : for variables or parameters sustitution
  • ; : end of command, another can start from here, new line is not necessary
  • ! : show execute command from history number
  • \ : wrap line at the end, the command continues in the next line, like a continuos line,
  • \ : cancel or scape the meaning of next character, ($,\,',",!,#,space,*) making this part of string ("\" - need another " to enclose)
  • ' : enclose text to avoid interprets of next characters ($,",\,#,space,;,",!,*)
  • " : enclose text to avoid interprets of characters (',;,#,\,space,) - (\") can not avoided
  • ",' : another character than space near of close quote is part of string ( " "abcd - is one string with spaces a begining)

Tips:
You can get enclose twice at the same string if close and open quote are near one to another. ('\') is \ enclosed, (' 'abcd) is an enclose espace plus letters, (' ''abcd') is the same, two enclosed together. ('\'') another ' needed to finish the second enclosure.
(\") enclosed betwen double quote is like an " Ej ("\"") ("\"""abcd" - two enclosed string near appear as one string.).

./print-arg.sh "ab'cd" 'ab"cd' 'ab''cd' "ab""cd" !645 "$HOME \$HOME \" !645 \!" '\" \$ $HOME !645'  
Arguments count : 7
All : ab'cd ab"cd abcd abcd ll /home/main $HOME " ll \! \" \$ $HOME !645 \# \# ab\ cd
0 -./print-arg.sh-
1 -ab'cd-
2 -ab"cd-
3 -abcd-
4 -abcd-
5 -ll-
6 -/home/main $HOME " ll \!-
7 -\" \$ $HOME !645-
8 -#-
9 -ab cd-

domingo, 23 de diciembre de 2012

Runnig rsync over ssh tunnel

I spend one whole day trying to run rsync client over ssh tunneling in one command. At this time I do it. The problem was a & symbol in LocalCommand option of ssh. There is the step for do it.

  1. Create key-pair certificate for non-interactive connection to server
  2. Configure and test client side for no-interactive ssh connection to server
  3. Load nobody user public certificate to server
  4. Configure ssh daemon in server for allow key-pair authentication for user nobody
  5. Test nobody connection to server
  6. Configure rsync server for accept only local connection
  7. Test rsync connection over ssh
# create certificate without password on client side
ssh-keygen -b 2048 -t rsa -f nobody.cert
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in nobody.cert.
Your public key has been saved in nobody.cert.pub.
The key fingerprint is:
28:50:07:52:56:63:35:44:10:57:53:79:bf:53:87:5b main@311c
The key's randomart image is:
+--[ RSA 2048]----+
|  ..=oB**.o...   |
|   + o o . .. .  |
|  .          . o |
|   .   .      . E|
|    . . S      o+|
|     .        .o |
|                .|
|                 |
|                 |
+-----------------+
# private key file nobody.cert and public key file nobody.cert.pub has been create

# create a ssh configuration file
vi $HOME\.ssh\config

# Fill with this
Host <server-ip>
user nobody
Hostname <server-ip>
IdentityFile /home/<user-name>/.ssh/nobody.cert
AddressFamily inet
#BatchMode yes
IdentitiesOnly yes
LocalForward 1873 127.0.0.1:873
ExitOnForwardFailure yes
SendEnv yes
PermitLocalCommand yes

# send public key to server using root or another account
scp nobody.cert.pub root@<server-ip>:/home/nobody/.ssh

# login to server and check nobody user configuration
grep nobody /etc/passwd       
nobody:x:501:501:Linux User,,,:/ffp/home/nobody:/ffp/bin/sh

#go to nobody home folder to add pub key into allowed keys
cd /ffp/home/nobody/.ssh
cat nobody.cert.pub >> authorized_keys

#configure ssh server daemon to allow key-pair authentication
vi /etc/ssh/sshd_config

# verify this lines
Protocol 2
RSAAuthentication yes
PubkeyAuthentication yes    
AuthorizedKeysFile        .ssh/authorized_keys
AllowTcpForwarding yes

# restart ssh server
/etc/init.d/ssh restart

# test connection to server
ssh <server-ip>
# If everything is ok password will not prompted and
# nobody user will be logged, else 
# try setting BatchMode to yes in config file to allows password prompt
# try setting IdentitiesOnly to false
# Check access to folder .ssh for nobody user

#change rsync server configuration file
vi /etc/rsyncd.conf

#put this line at beginning
address = 127.0.0.1

#restart rsync server
/etc/init.d/rsync restart

# try to connect to rsync over ssh with this command
export RSYNC_CMD='rsync -aqrut --port=1873 %d/working/ rsync://localhost/%u/ &'
ssh -o "LocalCommand=$RSYNC_CMD" <server-ip> sleep 2
# The directory $HOME/working will send to rsync 
# symbol & at end is very important, otherwise rsync client will be blocked for an unexplainable reason

# Good Luck

lunes, 5 de noviembre de 2012

Dlink DNS313 installing App

I will try to upgrade mi dlink dns313, because the torrent client that come with it do not support magic link for torrent files, in another hand y want to use a commun interfaz to communicate with torrent client.
Connect to dns313 data partition
to linux pc by usb cable.
mount the partitions in /mnt/p1...4

mount -t ext3 /dev/sdb3 /mnt/p3
mount -t ext2 /dev/sdb4 /mnt/p4
mount -t ntfs /dev/sdb2 /mnt/p2

cd /mnt/p2
wget http://www.inreto.de/dns323/fun-plug/0.5/fun_plug
wget http://www.inreto.de/dns323/fun-plug/0.5/fun_plug.tgz

vi fun_plug
# change FFP_PATH=/mnt/HD_a2/ffp
# by
# FFP_PATH=/mnt/HD_a4/ffp

umount /mnt/p*
restart dns313.

telnet

the first step is to change root password to avoid unautorizate access.
mkdir -p /ffp/home/root

#change shell and home folder of root
usermod -s /ffp/bin/sh root
usermod -d /ffp/home/root root
#The pwconv command creates shadow from passwd and an optionally existing shadow.
pwconv
passwd

The new password most be store in the nas, otherwise it will erase on next reboot.

cd /ffp/sbin
wget http://wolf-u.li/u/172/ -O store-password.sh

store-passwd.sh

#next step enable ssh and disable telnet.

cd /ffp/start
chmod a+x sshd.sh

./sshd.sh start
Generating public/private rsa1 key pair.
Your identification has been saved in /ffp/etc/ssh/ssh_host_key.
Your public key has been saved in /ffp/etc/ssh/ssh_host_key.pub.
The key fingerprint is:
.
.
.
Generating public/private rsa key pair.
Your identification has been saved in /ffp/etc/ssh/ssh_host_rsa_key.
Your public key has been saved in /ffp/etc/ssh/ssh_host_rsa_key.pub.
The key fingerprint is:

#try lo login using ssh
ssh -l root

cd /ffp/start

chmod a-x telnetd.sh

#restart device.
#Next: Install torrent client

#Step for install transmission torrent client
#login to device with ssh

#for fun-plug v0.5 do it
cd
wget http://kylek.is-a-geek.org:31337/files/ffp/0.5/curl-7.18.1.tgz
wget http://inreto.de/ffp/0.7/arm/packages/curl-7.21.4-arm-1.txz
funpkg -i curl-7.18.1.tgz
wget http://kylek.is-a-geek.org:31337/files/ffp/0.5/Transmission-2.71-1.tgz
funpkg -i Transmission-2.71-1.tgz

Run transmission for first time
-w download files to
-a allow connect from
-g config file
-p port for web or RPC  interface
-t autentication for web is required


 su nobody -c "transmission-daemon -f -g /mnt/HD_a2/.transmission-daemon -w /mnt/HD_a2/Downloads -t -u  -v  -a 127.0.0.1,192.168.1.*"

Use WebBrowser and navigate to
:9091

stop transmission client with CTRL-C and restart with

/ffp/start/transmission.sh start

******* unused ***********
user:root
password:11111

miércoles, 31 de octubre de 2012

NVIDIA ION in Linux at 311c

I try to install nvidia ion dirver in 311c Compaq Mini Atom pc. I downloaded file NVIDIA-Linux-x86-304.60.run.

chmod +x  NVIDIA-Linux-x86-304.60.run
./NVIDIA-Linux-x86-304.60

All are problems with this file. At end the folder /var/lib/dkms/nvidia/304.60 has been created. Problems with gcc version most be resolved with CC param.


cd /var/lib/dkms/nvidia/304.60/build
make CC=gcc-4.3
make CC=gcc-4.3 install


viernes, 26 de octubre de 2012

Linux installations in opt

For KDE Web Dev correct compilation use:


apt-get install kdelibs4c2a xorg-dev libqt3-headers kdelibs5-dev kdelibs4-dev
./configure --prefix=/opt/kdewebdev

Warning: you chose to install this package in /opt/kdewebdev,
but KDE was found in /usr.
For this to work, you will need to tell KDE about the new prefix, by ensuring that KDEDIRS contains it, e.g. export KDEDIRS=/opt/kdewebdev:/usr
Then restart KDE.

BlueFish:
apt-get install intltool
./configure --prefix=/opt/bluefish

Pinta:
apt-get install mono-xbuild

Linux local deb package directory

To build file Package.gz inside deb directory use:

cd to parent directory
build 

dpkg-scanpackages $directory-name /dev/null | gzip -9c > $directory-name/Packages.gz
 
Update /etc/apt/sources.list

deb file:$parent-directory/ $directory-name/

execute
apt-get update
 
Look for package from file
apt-get install apt-file
apt-file update

 
apt-file search $file-name 
 

jueves, 25 de octubre de 2012

Firefox - linux tips

Mi ordenador ATOM 1.6 con Debian/linux se vuelve un poco lento a veces. Cuando utilizas firefox hay instalado un plugin para flash de desarrollo libre que suele consumir mucho porcesador. su nonmbre gtk-snash. la solución pasa por eliminarlo de la forma.

apt-get remove --purge gnash gnash-common mozilla-plugin-gnash

domingo, 14 de octubre de 2012

Linux boot

Tengo la siguiente idea, instalar linux sobre un disco de sol lectura como puede ser una tarjeta SD, y dejar el disco solo para los operaciones de escritura, para ver si puedo reducir el consumo del ordenador y acelerar el acceso a ficheros.
Hay un sistema de archivo que permite hacer un espejo de una carpeta donde se realizan modificaciones pero solo se realiza en ram y al apagar el ordenador se regresa al estado anterior, esto puede ser util para que las aplicaciones que no cumplen con los estandares sobre estrutura en disco puedan operar con tranquilidad, tal es el caso de accesos de escritura sobre etc y otras carpetas del sistema que deberian poder ser utilizadas como read-only pero en la práctica esto no funciona de manera correcta.
Tambien esta la posibilidad, y esta idea es tomada de Android, de poder crear un estrutura de carpetas incial en el root fs, utilizando un punto de montaje cobre ram, para ello hay que estudiar bien el proceso de boot de linux, voy a resumir todo lo que aparece en internet sobre ello.
Hay tres etapas.
BIOS
MBR Master Boot Record
Kernel
Init.
Mi interes recide en las dos últimas. El kernel inicializa los dispositivos, monta el sistema de archivos raiz como solo lectura, se carga en memoria el sistema de archivos initrd, se ejecuta /linuxrc el cual carga los modulos, inicializa los dispositivos, y finaliza, luego es cargado el rootfs especificado por linea de comando y se ejecuta el /sbin/init, el cual utiliza el /etc/inittab.
Es necesario poder montar un sistema de achivos raiz y permitir ralizar modificaciones sobre el sin escribir realmente en disco. Tambien se pueden montar el rootfs en ram y luego montar el un sistema ro y realizar los links necesarios para el correcto funcionamiento del s.o.
continuara ...

viernes, 12 de octubre de 2012

Optimizando gnome

Cuando inicimio mi pequeño ordenador con procesador ATOM 1.7GHz, a pesar de terner instalado debian, el rendimiento no es el esperado por mi, además de estar el CPU casi siempre con alto uso, por ello el ventilador que lo refresca no para de girar.
Hoy e ejecutado top y he notado como un proceso llamado evince-tumbnails se comía toda la cpu, segun parece esto pertenece al gnome y se utiliza para generar algun que otro tipo de icono cuando navegamos por los directorios con un browser.
Hay una configuración de gnome que permite desabilitar este comportamiento, utilizando la aplicacion  gconf-editor se puede acceder a todos los parametros de gnome y el que me interesaba modificar es desktop/gnome/thumbnailers/disable_all , espero que le proxima vez al reiniciar el ordenador lo note un poco mas rápido.

miércoles, 12 de septiembre de 2012

Firefox & Citrix

Voy a comenzar un nuevo bloque de entradas sencillas y cortas que serán utilizadas para exponer mis problemas y soluciones del día a día. Y todo sera como un lugar donde buscar siempre las soluciones.
El primer problema al que me he enfrentado estos días y del cual no he logrado una solución definitiva, pero algo he podido resolver.
Me he conectado a un entorno remoto mediante Citrix y Firefox, pero al intentar ejecutar las aplicaciones publicadas, me dice que en el navegador que no estan habilitados los accesos directos. Hay que modificar la configuración de firefox utilizando la pagina about:config y buscar la keyword signed.applets.codebase_principal_support, y definir como valor true.
Esta configuración posibilita que se puedan ejecutar aplicaciones del sistema operativo desde applets en  firefox.

viernes, 17 de febrero de 2012

for vs while

Hoy quiero mostrar una pequeña diferencia entre el for y el while. Para ello vamos a empezar con el siguiente fragmento de código.

char	*cptr;
char	*texto = "abcdefghijklmnopq";
for (cptr=texto;*cptr;cptr++)
{
    printf("%c",*cptr);
}
cptr = texto;
while (*cptr)
{
   printf("%c",*cptr);
   cptr++;
}

Utilizando el compilador de Microsoft sin optimizaciones obtenemos.

for (cptr=texto;*cptr;cptr++)

  mov eax,dword ptr [ebp-14h]
  mov dword ptr [ebp-10h],eax
  jmp 1
2:mov ecx,dword ptr [ebp-10h]
  add ecx,1
  mov dword ptr [ebp-10h],ecx
1:mov edx,dword ptr [ebp-10h]
  movsx eax,byte ptr [edx]
  test eax,eax
  je 3
cptr = texto;
while (*cptr)
3:mov eax,dword ptr [ebp-14h]
  mov dword ptr [ebp-10h],eax
  
  
  
  
  mov ecx,dword ptr [ebp-10h]
  movsx edx,byte ptr [ecx]
  test edx,edx
  je 4
printf("%c",*cptr);
  mov ecx,dword ptr [ebp-10h]
  movsx edx,byte ptr [ecx]
  push edx
  push offset string "%c"
  call printf
  add esp,8
  jmp 2
printf("%c",*cptr);
  mov eax,dword ptr [ebp-10h]
  movsx ecx,byte ptr [eax]
  push ecx
  push offset string "%c"
  call printf
  add  esp,8
cptr++;
  mov edx,dword ptr [ebp-10h]
  add edx,1
  mov dword ptr [ebp-10h],edx
  jmp 3
4:

Como se aprecia utilizando el while hemos eliminado la instruccion jmp 1 el resto del código se mantiene inalterado. Este comportamiento que acabamos de observar se debe a que el ciclo for genera las instrucciones de lazo al comienzo.

Veamos que ocurre si necesitamos ignorar el tratamiento del caracter b, es decir este no sera mostrado por pantalla.

for (cptr=texto;*cptr;cptr++)
{
    if (*cptr =='b') continue;
    printf("%c",*cptr);
}
cptr = texto;
while (*cptr)
{
   if (*cptr != 'b')
   {
      printf("%c",*cptr);
   }
   cptr++;
}

Como podemos apreciar el uso del for genera un código mas limpio, ya que utilizando la sentencia continue se puede ir a la proxima iteración, mientras que en un while hay que invertir la condicion para poder ejecutar la instruccion de iteracion como si de un else se tratara.

for (cptr=texto;*cptr;cptr++)
 ... 
if (*cptr =='b') continue;
  mov ecx,dword ptr [ebp-10h]
  movsx edx,byte ptr [ecx]
  cmp   edx,62h
  jne   5
  jmp   2
printf("%c",*cptr);
5: ..
while (*cptr)
 ... 
if (*cptr != 'b')
  mov edx,dword ptr [ebp-10h]
  movsx eax,byte ptr [edx]
  cmp eax,62h
  je  6

cptr++;
6: ...

No me explico porque el compilador utilizo dos instrucciones de salto en el caso del for, de no haber sido asi los dos ejemplos serían idénticos. A modo de resumen utilizando el while hemos conseguido reducir una instruccion, si utilizamos la sentencia continue se genera la misma cantidad de instrucciones en los dos casos (o debería), eso si el codigo con while se vuelve mas extenso visualmente

En estos ejemplos sencillos donde se esta iterando una cadena de caracteres se pueden eliminar un par de instrucciones más si almacenamos el valor del caracter en una variable temporal.

char	c;
cptr = texto;
while (*cptr)
{
   c = *cptr;
   if (c != 'b')
   {
      printf("%c",c);
   }
   cptr++
}




if (*cptr !='b')
  mov edx,dword ptr [ebp-10h]
  movsx eax,byte ptr [edx]
  cmp eax,62h
  je  6
printf("%c",*cptr);
  mov ecx,dword ptr [ebp-10h]
  movsx edx,byte ptr [ecx]
  push edx
  push offset string "%c"
  call printf
  add esp,8
  jmp 2
c = *cptr;
  mov ecx,dword ptr [ebp-10h]
  mov dl,byte ptr [ecx]
  mov byte ptr [ebp-14h],dl
if (c != 'b')
  movsx edx,byte ptr [ebp-14h]
  cmp edx,62h
  je 6

printf("%c",c);
  movsx ecx,byte ptr [ebp-14h]
  push ecx
  push offset string "%c"
  call printf 
  add esp,8
  jmp 2

A pesar de utilizar 3 instrucciones para inicializar la variable c , cada vez que accedemos a *cptr nos ahorramos una instrucción, en tres accesos amortizamos el coste. Quiero aclarar que esto no es aplicable cuando iteramos entre estructuras. Se debe utilizar cuando accedemos continuamente a un mismo valor a traves de un puntero.

Vamos a realizar las ultimas modificaciones sobre el ciclo par intentar optimizarlo un poco mas, si el compilador nos ayuda claro esta.

while (c=*(cptr++))
{
   if (*cptr != 'b')
   { 
      printf("%c",*cptr);
   }
}

while (*cptr) {
c=*cptr;
cptr++;
  mov ecx,dword ptr [ebp-10h] 
  movsx edx,byte ptr [ecx]
  test edx,edx
  je 4 
  mov eax,dword ptr [ebp-10h] 
  mov cl,byte ptr [eax]
  mov byte ptr [ebp-14h],cl
  mov edx,dword ptr [ebp-10h] 
  add edx,1
  mov dword ptr [ebp-10h],edx 
while (c=*(cptr++)) {


  mov edx,dword ptr [ebp-10h] ecx = cptr 
  mov al,byte ptr [edx]
  mov byte ptr [ebp-14h],al   c = *cptr 
  movsx ecx,byte ptr [ebp-14h]
  mov edx,dword ptr [ebp-10h] cptr++ 
  add edx,1
  mov dword ptr [ebp-10h],edx
  test ecx,ecx                c == 0 
  je  4

Como imaginaba, el compilador no ha sido de gran ayuda, ya que ha cargado el valor de cptr en edx por segunda vez sin necesidad; además en vez de utilizar un test al,al que ya contiene el valor de c, ha utilizado ecx resultando en una instruccion más. A pesar de todo el código es una instruccion menor. Eso si aqui se ha hecho una comparación desigual, pero no va mejorar mucho si utilizamos c = *(cptr++);en el código de la izquierda.

No me cabe duda el compilador no optimiza lo mas obvio, es cierto que he compilado sin optimizaciones. De todas formas voy a realizar pruebas utilizando gcc o g++ y expondre mis resultados

jueves, 17 de noviembre de 2011

Array index vs pointer I

Como muchos de vosotros yo suelo utilizar un puntero para iterar los datos de un array. Pero hace poco un amigo me dijo: "¿Por qué lo haces así y no utilizas un índice?". A lo que yo respondí, "Pues porque es más optimo".Mi amigo mostró su disconformidad y nos enzarzamos en una discusión que, como casi todas hoy en día, hemos tratado de resolver buscando la respuesta en internet. Y he de confesar que ésta la tengo medio perdida. Cuando buscas en Google la frase “array index vs pointer”, se encuentran documentos donde se plantea que no hay muchas diferencias; pruebas realizadas demuestran que se emplea el mismo tiempo tanto si se itera con índice como con punteros. El por qué de este resultado depende de muchos factores, entre ellos (el compilador utilizado, las optimizaciones, el tipo de procesador y el código fuente que se utiliza entre otros). En este artículo voy explicar como afectan los diferentes factores a la iteración de datos en un array. Tomemos como base el siguiente código en C.

char *texto = "1234567890";
int len = strlen(texto);
for (int j =0;j < len;j++)
    if (texto[j] == '6') break;
for (char* cptr=texto;*cptr != 0;cptr++)
    if (*cptr == '6') break;

Utilizando el compilador de Microsoft para x86 32Bits y sin aplicar optimizaciones se obtiene el siguiente código en ensamblador.

int len = strlen(texto);
   mov eax,dword ptr [texto]
   push eax
   call strlen (00416d70)
   add esp,4
   mov dword ptr [len],eax
for (int j=0;j< len;j++) for (char* cptr=texto;*cptr != 0;cptr++)
   mov dword ptr [j],0
   jmp 1
2: mov edx,dword ptr [j]
   add edx,1
   mov dword ptr [j],edx
1: mov eax,dword ptr [j]
   cmp eax,dword ptr [len]
   jge 4
j = 0
(j < len)
edx=j
edx++
j=edx
eax=j
eax < len
break
   mov eax,dword ptr [texto]
   mov dword ptr [cptr],eax
   jmp 1
2: mov ecx,dword ptr [cptr]
   add ecx,1
   mov dword ptr [cptr],ecx
1: mov edx,dword ptr [cptr]
   movsx eax,byte ptr [edx]
   test eax,eax
   je 4
ax = &texto
cptr = aex
(*cptr != 0)
ecx = cptr
ecx ++
cptr = ecx
edx = cptr
eax = *cptr
eax == 0
break
if (texto[j] =='6') break; if (*cptr == '6') break;
   mov ecx,dword ptr [texto]
   add ecx,dword ptr [j]
   movsx edx,byte ptr [ecx]
   cmp edx,36h
   jne 3
   jmp 4
3: jmp 2
4:
cx =&texto
ecx= texto+j
edx = texto[j]
edx == 6
else
break
( j++ )
   mov ecx,dword ptr [cptr]
   movsx edx,byte ptr [ecx]
   cmp edx,36h
   jne 3
   jmp 4
3: jmp 2
4:
ecx = cptr
edx = *cptr
edx == '6'
else 
break;
( cptr++ )
IndicePuntero
Inicialización73
Condición ciclo34
Incremento33
Instrucciones76
Total ciclo1313

Analizando el final del código podemos apreciar como el compilador ha pasado por alto una optimización muy sencilla; las instrucciones (jne 3 , jmp4, jmp 2) son equivalente a (je 4, jmp 2). La cantidad de instrucciones que se utilizan para la Inicialización, condición, incremento y realización del ciclo se resumen en la tabla. Lo cual permite concluir que los dos ciclos son igual de rápidos; pero hemos olvidado algo importante; el ciclo con indice no es exactamente igual al ciclo con punteros, el código correcto se muestra a continuación.

for (int j=0;texto[j]!=0;j++)
   mov dword ptr [j],0
   jmp 1
2: mov edx,dword ptr [j]
   add edx,1
   mov dword ptr [j],edx
1: mov edx,dword ptr [texto]
   add edx,dword ptr [j]
   movsx eax,byte ptr [edx]
   test eax,eax
   jge 4
j = 0
( texto[j]!=0 )
edx=j
edx++
j=edx
edx=texto
edx=&texto[j]
eax=texto[j]
texto[j] == 0
break
if (texto[j] == '6') break;
   mov ecx,dword ptr [texto]
   add ecx,dword ptr [j]
   movsx edx,byte ptr [ecx]
   cmp edx,36h
   jne 3
   jmp 4
3: jmp 2
4:
ecx = &texto
ecx= texto+j
edx = texto[j]
edx == 6
else
break
( j++ )

Con este nuevo desarrollo hemos eliminado la llamada a la función strlen, la inicialización del ciclo es más rápida por una instrucción, pero la condición de ciclo es mas lenta; de momento la ventaja es para el puntero, pero por muy poco margen. Vamos a introducir una complejidad extra al ciclo, en lugar de iterar un sencillo array de char vamos a iterar una estructura.

struct S {
int a;
char c;
BYTE b;
};
struct S aS[] = {
  {1,'a',1},
  {2,'b',2},
  {3,'b',4},
  {0,0,0} };
struct S *pS;
int j;
for (j=0;aS[j].a != 0;j++)
  if (aS[j].a == 3) break;
for (pS =aS;pS->a != 0;pS++)
  if (pS->a == 3) break;
for (j=0;aS[j].a != 0;j++) for (pS = aS;pS->a != 0;pS++)
   mov dword ptr [j],0
   jmp 2
1: mov edx,dword ptr [j]
   add edx,1
   mov dword ptr [j],edx
2: mov eax,dword ptr [j]
   imul eax,eax,1Ch
   cmp dword ptr aS[eax],0
je 4
j=0
(aS[j].a != 0)
edx = j
edx ++;
j = edx
eax = j
eax=eax*0x1C
aS[j].a != 0
break;
   lea edx,[aS]
   mov dword ptr [pS],edx
   jmp 2
1: mov eax,dword ptr [pS]
   add eax,1Ch
   mov dword ptr [pS],eax
2: mov ecx,dword ptr [pS]
   cmp dword ptr [ecx],0
je 4
edx=aS
pS=edx
condición
eax=pS
eax+=0x1C
pS=eax
ecx=pS
(pS-<a != 0)
break
if (aS[j].a == 3) break; if (pS->a == 3) break;
   mov ecx,dword ptr [j]
   imul ecx,ecx,1Ch
   cmp dword ptr aS[ecx],3
   jne 3
   jmp 4
3: jmp 1
4:
ecx=j
ecx*=0x1C
aS[j].a == 3
else
break;
continue;
   mov edx,dword ptr [pS]
   cmp dword ptr [edx],3
   jne 3
   jmp 4
3: jmp 1
4:
edx=pS
pS-<a==3
else
break;
continue;
IndicePuntero
Inicialización23
Condición ciclo43
Incremento33
Instrucciones65
Total ciclo1311

Cuando iteramos una estructura el direccionamiento por indice requiere una istrucción de multiplicación que penaliza el rendimiento. Hay un aspecto mas a tener en cuenta; la variable "a" de la estructura es de tipo entero y está justo al incio, veamos la diferencia si es de tipo BYTE y se encuentra al final

struct S {
int b;
char c;
BYTE a;
};
struct S aS[] = {
  {1,'a',1},
  {2,'b',2},
  {3,'b',4},
  {0,0,0} };
struct S *pS;
int j;
for (j=0;aS[j].fin != 0;j++)
  if (aS[j].a == 3) break;
for (pS =aS;pS->fin != 0;pS++)
  if (pS->a == 3) break;
for (j=0;aS[j].a != 0;j++) for (pS = aS;pS->a != 0;pS++)
   mov dword ptr [j],0
   jmp 2
1: mov edx,dword ptr [j]
   add edx,1
   mov dword ptr [j],edx
2: mov eax,dword ptr [j]
   imul eax,eax,1Ch
   xor ecx,ecx
   mov cl,byte ptr [ebp+eax-0BF3h]
   test ecx,ecx
   je 4
j=0
( aS[j].a != 0 )
edx = j
edx++
j = edx
eax = j
eax *= 0x1C
ecx = 0
cl = as[j].a
cl == 0
break
   lea ecx,[aS]
   mov dword ptr [pS],ecx
   jmp 2
1: mov edx,dword ptr [pS]
   add edx,1Ch
   mov dword ptr [pS],edx
2: mov eax,dword ptr [pS]
   xor ecx,ecx
   mov cl,byte ptr [eax+19h]
   test ecx,ecx
   je 4
ecx = aS
cptr = ecx
( pS->a != 0 )
edx = pS
edx += 0x1C
pS = edx
eax = pS
ecx = 0
cl = pS->a
ecx == 0
break
if (aS[j].a == 3) break; if (pS->a == 3) break;
   mov edx,dword ptr [j]
   imul edx,edx,1Ch
   xor eax,eax
   mov al,byte ptr [ebp+edx-0BF3h]
   cmp eax,3
   jne 3
   jmp 4
3: jmp 1
4:
edx = j
edx *= 0x1C
eax = 0
al = aS[j].a
al == 3
else
break
( j++ )
   mov edx,dword ptr [pS]
   xor eax,eax
   mov al,byte ptr [edx+19h]
   cmp eax,3
   jne 3
   jmp 4
3: jmp 1
4:
edx = pS
eax = 0
al = pS->a
eax == 3
else
break
( pS ++ )
IndicePuntero
Inicialización23
Condición ciclo65
Incremento33
Instrucciones87
Total ciclo1715

El código es mas complejo, pues un byte no se puede comparar directamente desde memoria, para ello hay que cargarlo en un registro (ecx) que previamente debe estar en cero, además hay que utilizar un desplazamiento de 0x19 para acceder a la variable "a". Este ejemplo a incrementado por igual el tamaño de los ciclos. A medida que el ciclo se hace complejo el factor que determina la diferencia es la velocidad para acceder a un elemento y sus datos. En la siguiente publicación abordare este tema

Como resumen podemos decir que: Acceder a la memoria con puntero es una instrucción más rápido que con indice y si mutliplicamos por la cantidad de iteraciones del ciclo y las veces que accedemos, la diferencia puede ser notable; además datos de tipo BYTE añade complejida al código ensamblador.

Presentación

   Soy desarrollador en C, C++, ensamblador. Pero además poco a poco me voy convirtiendo en un dinosaurio.
     Y es que yo soy de aquellos que empezaron programando un Z-80 en lenguaje máquina... en los tiempos en que se utilizaba un televisor gigante, en blanco y negro, que se conectaba por cable de antena al ordenador y una cinta de música para grabar los programas. Mi siguiente lenguaje fue el Turbo Pascal. Utilizaba unos ordenadores que se tomaban su tiempo para contar 640KB de memoria, y luego hacían el boot desde un disco de 5 1/4. Recuerdo tener una caja de unos 10 discos cada uno con un programa diferente.

     Ya en la universidad apareció el Windows 3.11, pero yo seguí con el Turbo Pascal hasta que un día pasé al entorno de desarrollo Delphi. Estuve un par de años haciendo varios desarrollos que utilizaban el puerto serie RS232, hasta que me presentaron el C++ y el entorno de desarrollo Microsoft Visual Studio 6.0. Al principio todo eran preguntas, que si MFC, que si base de datos... Un año después ya había desarrollado aplicaciones utilizando OpenGL, base de datos, Comunnicación Serie etc. En aquellos tiempos también conocí el procesador 8051 de intel, tuve que programar mucho en ensamblador, hasta el punto de tener que contar cuantas instrucciones se estaban ejecutando en un interrupción y descubrir con sorpresa "huy pero si no me da tiempo a hacer todo antes de la próxima interrupcion por x milisegundos...".

     Después de tantos años soy una persona que desarrolla en C y C++ pensando como si lo hiciera un ensamblador. Para mí una variable o una instrucción menos es importante, ese milisegundo de más en un ordenador a 2.1Ghz es importante. La forma de procesar la informacion moviéndola lo menos posible de un lugar a otro también lo es.

     En todos estos años desarrollando, los miles de problemas que he tenido me han hecho Reinventar la Rueda, he hecho todo lo posible por evitar un new y un delete. Estoy intentando desarrollar aplicaciones compatibles LINUX, WINDOWS.

    Todas mis ideas y creaciones las iré reflejando en este blog, para que esten al alcance de todos, realimentarme con la comunidad y aprender un poco más cada día.