Seite 1 von 2

Performance und saubere Programmierung

Verfasst: Mi 5. Jan 2011, 09:18
von rethus
Hallo Leute,

ich schraube gerade etwas an den Core-Codes rum, um Contenido an meine Bedürfnisse anzupassen. Dabei habe ich eine Frage zum Programmierstyle und in Verbindung mit Performance.

Nehmen wir mal die Datei include.Mod_overview.php im Verzeichnis includes:

Code: Alles auswählen

<?php
/**
 * Project: 
 * Contenido Content Management System
 * 
 * Description: 
 * Module list
 * 
 * Requirements: 
 * @con_php_req 5.0
 * 
 *
 * @package    Contenido Backend includes
 * @version    1.3.2
 * @author     unknown
 * @copyright  four for business AG <www.4fb.de>
 * @license    http://www.contenido.org/license/LIZENZ.txt
 * @link       http://www.4fb.de
 * @link       http://www.contenido.org
 * @since      file available since contenido release <= 4.6
 * 
 * {@internal 
 *   created 2003-03-21
 *   modified 2008-06-27, Frederic Schneider, add security fix
 *   modified 2010-08-18, Munkh-Ulzii Balidar, add a functionality to show the used info
 *
 *   $Id: include.mod_overview.php 1224 2010-10-12 08:59:42Z dominik.ziegler $:
 * }}
 * 
 */

if(!defined('CON_FRAMEWORK')) {
	die('Illegal call');
}

cInclude("classes", "class.htmlelements.php");
cInclude("classes", "class.todo.php");
cInclude("classes", "contenido/class.module.php");
cInclude("classes", "contenido/class.user.php");
cInclude("classes", "widgets/class.widgets.page.php");
cInclude("classes", "widgets/class.widgets.foldingrow.php");
cInclude("classes", "widgets/class.widgets.pager.php");
cInclude("classes", "class.ui.php");

if (!(int) $client > 0) {
  #if there is no client selected, display empty page
  $oPage = new cPage;
  $oPage->render();
  return;
}

############################
# Now build bottom with list
############################
$cApiModuleCollection	= new cApiModuleCollection;
$classmodule			    = new cApiModule;
$oPage					      = new cPage;

// no value found in request for items per page -> get form db or set default
$oUser = new cApiUser($auth->auth["uid"]);
if (!isset($_REQUEST["elemperpage"]) || !is_numeric($_REQUEST['elemperpage']) || $_REQUEST['elemperpage'] < 0) 
{
	$_REQUEST["elemperpage"] = $oUser->getProperty("itemsperpage", $area);
}
if (!is_numeric($_REQUEST["elemperpage"])) 
{
	$_REQUEST["elemperpage"] = 0;
}
if ($_REQUEST["elemperpage"] > 0) 
{
	// -- All -- will not be stored, as it may be impossible to change this back to something more useful
	$oUser->setProperty("itemsperpage", $area, $_REQUEST["elemperpage"]);
}
unset ($oUser);

if (!isset($_REQUEST["page"]) || !is_numeric($_REQUEST['page']) || $_REQUEST['page'] <= 0 || $_REQUEST["elemperpage"] == 0) 
{
	$_REQUEST["page"] = 1;
}

// Sort by requested
if (isset ($_REQUEST["sortby"]) && $_REQUEST["sortby"] != "")
{
	$cApiModuleCollection->setOrder($_REQUEST["sortby"]." ".$_REQUEST["sortorder"]);
} 
else
{
	$cApiModuleCollection->setOrder("name asc");
}


// Search filter requested
if (isset ($_REQUEST["filter"]) && $_REQUEST["filter"] != ""  && $_REQUEST["searchin"] == '')
{
	$cApiModuleCollection->setWhereGroup("default", "name", "%".$_REQUEST["filter"]."%", "LIKE");
	$cApiModuleCollection->setWhereGroup("default", "description", "%".$_REQUEST["filter"]."%", "LIKE");
	$cApiModuleCollection->setWhereGroup("default", "type", "%".$_REQUEST["filter"]."%", "LIKE");
	$cApiModuleCollection->setWhereGroup("default", "input", "%".$_REQUEST["filter"]."%", "LIKE");
	$cApiModuleCollection->setWhereGroup("default", "output", "%".$_REQUEST["filter"]."%", "LIKE");
	$cApiModuleCollection->setInnerGroupCondition("default", "OR");
} else if (isset ($_REQUEST["filter"]) && $_REQUEST["filter"] != ""  && $_REQUEST["searchin"] == 'name') {
    $cApiModuleCollection->setWhereGroup("default", "name", "%".$_REQUEST["filter"]."%", "LIKE");
    $cApiModuleCollection->setInnerGroupCondition("default", "OR");
} else if (isset ($_REQUEST["filter"]) && $_REQUEST["filter"] != ""  && $_REQUEST["searchin"] == 'description') {
    $cApiModuleCollection->setWhereGroup("default", "description", "%".$_REQUEST["filter"]."%", "LIKE");
    $cApiModuleCollection->setInnerGroupCondition("default", "OR");
} else if (isset ($_REQUEST["filter"]) && $_REQUEST["filter"] != ""  && $_REQUEST["searchin"] == 'type') {
    $cApiModuleCollection->setWhereGroup("default", "type", "%".$_REQUEST["filter"]."%", "LIKE");
    $cApiModuleCollection->setInnerGroupCondition("default", "OR");
} else if (isset ($_REQUEST["filter"]) && $_REQUEST["filter"] != ""  && $_REQUEST["searchin"] == 'input') {
    $cApiModuleCollection->setWhereGroup("default", "input", "%".$_REQUEST["filter"]."%", "LIKE");
    $cApiModuleCollection->setInnerGroupCondition("default", "OR");
} else if (isset ($_REQUEST["filter"]) && $_REQUEST["filter"] != ""  && $_REQUEST["searchin"] == 'output') {
    $cApiModuleCollection->setWhereGroup("default", "output", "%".$_REQUEST["filter"]."%", "LIKE");
    $cApiModuleCollection->setInnerGroupCondition("default", "OR");
}

// Type filter requested
if (isset($_REQUEST["filtertype"]))
{
	switch ($_REQUEST["filtertype"])
	{
		case "--all--":
			break;
		case "--wotype--":
			$cApiModuleCollection->setWhere("type", "");
			break;
		default:
			$cApiModuleCollection->setWhere("type", $_REQUEST["filtertype"]);
			break;	
	}
}

// Items per page requested
$cApiModuleCollection->setWhere("idclient", $client);


if ($_REQUEST["elemperpage"] > 0) 
{
	$cApiModuleCollection->query();
	$iItemCount = $cApiModuleCollection->count();

	if ($iItemCount < (($_REQUEST["page"] - 1) * $_REQUEST["elemperpage"])) 
	{
		$_REQUEST["page"] = 1;
	}
    
    if ($_REQUEST["elemperpage"]*($_REQUEST["page"]) >= $iItemCount+$_REQUEST["elemperpage"] && $_REQUEST["page"]  != 1) {
        $_REQUEST["page"]--;
    }

	$cApiModuleCollection->setLimit(($_REQUEST["elemperpage"] * ($_REQUEST["page"] -1)), $_REQUEST["elemperpage"]);
} 
else 
{
	$iItemCount 		= 0;
}


// Build list for left_bottom considering filter values
$mlist 				      = new UI_Menu;
$sOptionModuleCheck	= getSystemProperty("system", "modulecheck");
$sOptionForceCheck	= getEffectiveSetting("modules", "force-menu-check", "false");
$iMenu				= 0;

$cApiModuleCollection->query();
while ($cApiModule = $cApiModuleCollection->next())
{
	if ($perm->have_perm_item($area, $db->f("idmod")) || $perm->have_perm_area_action("mod_translate", "mod_translation_save") || $perm->have_perm_area_action_item("mod_translate", "mod_translation_save", $cApiModule->get("idmod")))
	{
			$idmod = $cApiModule->get("idmod");
			
			$link = new cHTMLLink;
			$link->setMultiLink("mod", "", "mod_edit", "");
			$link->setCustom("idmod", $cApiModule->get("idmod"));
			$link->updateAttributes(array ("alt" => $cApiModule->get("description")));
			$link->updateAttributes(array ("title" => $cApiModule->get("description")));
			$link->updateAttributes(array ("style" => "margin-left:5px"));

			$sName = $cApiModule->get("name");

			if ($sOptionModuleCheck !== "false" && $sOptionForceCheck !== "false")
			{
				// Check module and force check has been enabled - check module (surprisingly...)
				$inputok = modTestModule($cApiModule->get("input"), $cApiModule->get("idmod")."i", false);
				$outputok = modTestModule($cApiModule->get("output"), $cApiModule->get("idmod")."o", true);

				if ($inputok && $outputok)		// Everything ok
				{
					$colName = $sName;			// The set default color: none :)
				}
				else if ($inputok || $outputok)	// Input or output has a problem
				{
					$colName = '<font color="#B1AC58">'.$sName.'</font>';
				}
				else							// Input >and< output has a problem
				{
					$colName = '<font color="red">'.$sName.'</font>';
				}
			}
			else
			{
				// Do not check modules (or don't force it) - so, let's take a look into the database 
				$sModuleError = $cApiModule->get("error");
				
				if ($sModuleError == "none")
				{
					$colName = $sName;
				} 
				else if ($sModuleError == "input" || $sModuleError == "output")
				{
					$colName = '<font color="#B1AC58">'.$sName.'</font>';
				} 
				else
				{
					$colName = '<font color="red">'.$sName.'</font>';
				}
			}

			$iMenu ++;

			$mlist->setTitle($iMenu, $colName);
			if ($perm->have_perm_area_action_item("mod_edit", "mod_edit", $db->f("idmod")) || $perm->have_perm_area_action_item("mod_translate", "mod_translation_save", $cApiModule->get("idmod")))
			{
				$mlist->setLink($iMenu, $link);
			}

			$inUse = $classmodule->moduleInUse($idmod);

			$deletebutton = "";
			
			if ($inUse)
			{
				$inUseString = i18n("In use");
				$mlist->setActions($iMenu, 'inuse', '<a href="javascript:;" rel="' . $idmod . '" class="in_used_mod"><img src="'.$cfg['path']['images'].'exclamation.gif" border="0" title="'.$inUseString.'" alt="'.$inUseString.'"></a>');
				$delDescription = i18n("Module in use, cannot delete");
				
			} else {
                $mlist->setActions($iMenu, 'inuse', '<img src="./images/spacer.gif" border="0" width="16">');
				if ($perm->have_perm_area_action_item("mod", "mod_delete", $cApiModule->get("idmod")))
				{
				$delTitle = i18n("Delete module");
				$delDescr = sprintf(i18n("Do you really want to delete the following module:<br><br>%s<br>"), $sName);

				$deletebutton = '<a title="'.$delTitle.'" href="javascript://" onclick="box.confirm(\''.$delTitle.'\', \''.$delDescr.'\', \'deleteModule('.$idmod.')\')"><img src="'.$cfg['path']['images'].'delete.gif" border="0" title="'.$delTitle.'" alt="'.$delTitle.'"></a>';				
				} else {
					$delDescription = i18n("No permission");	
				}
			}
			
			if ($deletebutton == "")
			{
                //$deletebutton = '<img src="images/spacer.gif" width="16" height="16">';
				$deletebutton = '<img src="'.$cfg['path']['images'].'delete_inact.gif" border="0" title="'.$delDescription.'" alt="'.$delDescription.'">';	
			}
			
			$todo = new TODOLink("idmod", $db->f("idmod"), "Module: $sName", "");
			
			$mlist->setActions($iMenu, "todo", $todo->render());
			$mlist->setActions($iMenu, "delete", $deletebutton);
            
            if ($_GET['idmod'] == $idmod) {
                $mlist->setExtra($iMenu, 'id="marked" ');
            }     
			//$mlist->setImage($iMenu, "images/but_module.gif");
			//$mlist->setImage($iMenu, 'images/spacer.gif', 5);
		}
}

$deleteScript = '    <script type="text/javascript">

        var sid = "'.$sess->id.'";
        box = new messageBox("", "", "", 0, 0);

        function deleteModule(idmod) {

        //console.log(parent.frames[1].document.filter.sortorder);
        
  			form = document.getElementById("filter");

        url  = \'main.php?area=mod_edit\';
        url += \'&action=mod_delete\';
        url += \'&frame=4\';
        url += \'&idmod=\' + idmod;
        url += \'&contenido=\' + sid;
        url += get_registered_parameters();
        url += \'&sortby=\' + parent.frames[1].document.filter.sortby;
				url += \'&sortorder=\' + parent.frames[1].document.filter.sortorder;
				url += \'&filter=\' + parent.frames[1].document.filtertype;
				url += \'&elemperpage=\' + parent.frames[1].document.filter.elemperpage;
				url += \'&page=\' + parent.frames[1].document.filter.page;
				parent.parent.right.right_bottom.location.href = url;
        }

    </script>';

$sShowUsedInfo = ' 
		<script type="text/javascript">       
			$(document).ready(function() {
				
	        	$(".in_used_mod").live("click", function() {
	            	var iId = $(this).attr("rel");
	            	if (iId) {
	            		$.post(
	            		   "' . $cfg['path']['contenido_fullhtml'] . 'ajaxmain.php' . '", 
	      				   { area: "' . $area . '", ajax: "inused_module", id: iId, contenido: sid }, 
	      				   function(data) {
	      					  box.notify("' . i18n("Is used in") . ':", data);
	      				   } 
	      				);
	            	}	
	        	});
	        });
        </script>
        ';

$sMarkRow = '<script language="javascript">    
                if (document.getElementById(\'marked\')) {
                    row.click(document.getElementById(\'marked\'));
                }
            </script>';
    
$oPage->setMargin(0);
$oPage->addScript('messagebox', '<script type="text/javascript" src="scripts/messageBox.js.php?contenido='.$sess->id.'"></script>');
$oPage->addScript('jquery', '<script type="text/javascript" src="scripts/jquery/jquery.js"></script>');
$oPage->addScript('delete', $deleteScript);
$oPage->addScript('showUsedInfo', $sShowUsedInfo);
$oPage->addScript('cfoldingrow.js', '<script language="JavaScript" src="scripts/cfoldingrow.js"></script>');
$oPage->addScript('parameterCollector.js', '<script language="JavaScript" src="scripts/parameterCollector.js"></script>');
$oPage->setContent($mlist->render(false).$sMarkRow);

//generate current content for Object Pager
$oPagerLink = new cHTMLLink;
$pagerl="pagerlink";
$oPagerLink->setTargetFrame('left_bottom');
$oPagerLink->setLink("main.php");
$oPagerLink->setCustom("elemperpage", $elemperpage);
$oPagerLink->setCustom("filter", stripslashes($_REQUEST["filter"]));
$oPagerLink->setCustom("sortby", $_REQUEST["sortby"]);
$oPagerLink->setCustom("sortorder", $_REQUEST["sortorder"]);
$oPagerLink->setCustom("frame", $frame);
$oPagerLink->setCustom("area", $area);
$oPagerLink->enableAutomaticParameterAppend();
$oPagerLink->setCustom("contenido", $sess->id);
$oPager = new cObjectPager("02420d6b-a77e-4a97-9395-7f6be480f497", $iItemCount, $_REQUEST["elemperpage"], $_REQUEST["page"], $oPagerLink, "page", $pagerl);

//add slashes, to insert in javascript
$sPagerContent = $oPager->render(1);
$sPagerContent = str_replace('\\', '\\\\', $sPagerContent);
$sPagerContent = str_replace('\'', '\\\'', $sPagerContent);

//send new object pager to left_top
$sRefreshPager = '
    <script type="text/javascript">
        var sNavigation = \''.$sPagerContent.'\';
        var left_top = parent.left_top;
        if (left_top.document) {
            var oPager = left_top.document.getElementById(\'02420d6b-a77e-4a97-9395-7f6be480f497\');
            if (oPager) {
                oInsert = oPager.firstChild;
                oInsert.innerHTML = sNavigation;
                left_top.toggle_pager(\'02420d6b-a77e-4a97-9395-7f6be480f497\');
            }
        }
    </script>';
            
$oPage->addScript('refreshpager', $sRefreshPager); 

$oPage->render();
?>
Hier werden Werte aus der DB-Abfrage mehrfach genutzt. Doch anstatt diese Werte einmal in eine lokale Variable zu überführen, wird diese immer wieder mittels $db->f(' ') ausgelesen. Abgesehen von der längeren Schreibweise frage ich mich, ob es nicht performanter wäre, zu beginn der/des Schleife/Scripts entsprechende Werte in eine Variable zu schreiben.
Scheinbar ist der Ansatz hier auch schon gegeben, da es im Code folgende Zeile gibt:

Code: Alles auswählen

$idmod = $cApiModule->get("idmod");
Seltsamerweise wird jedoch anstatt $idmod nachfolgend immer wieder mit $db-F('idmod') die DB abgefragt.
Frage: Gibt es einen Grund dafür? Wäre es nicht performatner den Weg der "lokalen variable" zu gehen?

Hier ein Codeauszug der Whileschleife:

Code: Alles auswählen

while ($cApiModule = $cApiModuleCollection->next())
{
   if ($perm->have_perm_item($area, $db->f("idmod")) || $perm->have_perm_area_action("mod_translate", "mod_translation_save") || $perm->have_perm_area_action_item("mod_translate", "mod_translation_save", $cApiModule->get("idmod")))
   {
         $idmod = $cApiModule->get("idmod");
         
         $link = new cHTMLLink;
         $link->setMultiLink("mod", "", "mod_edit", "");
         $link->setCustom("idmod", $cApiModule->get("idmod"));
         $link->updateAttributes(array ("alt" => $cApiModule->get("description")));
         $link->updateAttributes(array ("title" => $cApiModule->get("description")));
         $link->updateAttributes(array ("style" => "margin-left:5px"));

         $sName = $cApiModule->get("name");

         if ($sOptionModuleCheck !== "false" && $sOptionForceCheck !== "false")
         {
            // Check module and force check has been enabled - check module (surprisingly...)
            $inputok = modTestModule($cApiModule->get("input"), $cApiModule->get("idmod")."i", false);
            $outputok = modTestModule($cApiModule->get("output"), $cApiModule->get("idmod")."o", true);

            if ($inputok && $outputok)      // Everything ok
            {
               $colName = $sName;         // The set default color: none :)
            }
            else if ($inputok || $outputok)   // Input or output has a problem
            {
               $colName = '<font color="#B1AC58">'.$sName.'</font>';
            }
            else                     // Input >and< output has a problem
            {
               $colName = '<font color="red">'.$sName.'</font>';
            }
         }
         else
         {
            // Do not check modules (or don't force it) - so, let's take a look into the database
            $sModuleError = $cApiModule->get("error");
            
            if ($sModuleError == "none")
            {
               $colName = $sName;
            }
            else if ($sModuleError == "input" || $sModuleError == "output")
            {
               $colName = '<font color="#B1AC58">'.$sName.'</font>';
            }
            else
            {
               $colName = '<font color="red">'.$sName.'</font>';
            }
         }

         $iMenu ++;

         $mlist->setTitle($iMenu, $colName);
         if ($perm->have_perm_area_action_item("mod_edit", "mod_edit", $db->f("idmod")) || $perm->have_perm_area_action_item("mod_translate", "mod_translation_save", $cApiModule->get("idmod")))
         {
            $mlist->setLink($iMenu, $link);
         }

         $inUse = $classmodule->moduleInUse($idmod);

         $deletebutton = "";
         
         if ($inUse)
         {
            $inUseString = i18n("In use");
            $mlist->setActions($iMenu, 'inuse', '<a href="javascript:;" rel="' . $idmod . '" class="in_used_mod"><img src="'.$cfg['path']['images'].'exclamation.gif" border="0" title="'.$inUseString.'" alt="'.$inUseString.'"></a>');
            $delDescription = i18n("Module in use, cannot delete");
            
         } else {
                $mlist->setActions($iMenu, 'inuse', '<img src="./images/spacer.gif" border="0" width="16">');
            if ($perm->have_perm_area_action_item("mod", "mod_delete", $cApiModule->get("idmod")))
            {
            $delTitle = i18n("Delete module");
            $delDescr = sprintf(i18n("Do you really want to delete the following module:<br><br>%s<br>"), $sName);

            $deletebutton = '<a title="'.$delTitle.'" href="javascript://" onclick="box.confirm(\''.$delTitle.'\', \''.$delDescr.'\', \'deleteModule('.$idmod.')\')"><img src="'.$cfg['path']['images'].'delete.gif" border="0" title="'.$delTitle.'" alt="'.$delTitle.'"></a>';            
            } else {
               $delDescription = i18n("No permission");   
            }
         }
         
         if ($deletebutton == "")
         {
                //$deletebutton = '<img src="images/spacer.gif" width="16" height="16">';
            $deletebutton = '<img src="'.$cfg['path']['images'].'delete_inact.gif" border="0" title="'.$delDescription.'" alt="'.$delDescription.'">';   
         }
         
         $todo = new TODOLink("idmod", $db->f("idmod"), "Module: $sName", "");
         
         $mlist->setActions($iMenu, "todo", $todo->render());
         $mlist->setActions($iMenu, "delete", $deletebutton);
           
            if ($_GET['idmod'] == $idmod) {
                $mlist->setExtra($iMenu, 'id="marked" ');
            }     
         //$mlist->setImage($iMenu, "images/but_module.gif");
         //$mlist->setImage($iMenu, 'images/spacer.gif', 5);
      }
}

Re: Performance und saubere Programmierung

Verfasst: Mi 5. Jan 2011, 09:45
von kummer
rethus hat geschrieben:Frage: Gibt es einen Grund dafür? Wäre es nicht performatner den Weg der "lokalen variable" zu gehen?
jein. und ja. einen grund wird es dafür geben; aber leistungsoptimierung gehört sicher nicht dazu. und ja, das einmalige fetchen ist in diesem zusammenhang ein must. will man das ganze performant, muss man folgende regeln befolgen:
  1. in jedem request genau 1 einzige db-verbindung aufbauen. das system müsste den aufbau einer weiteren verbindung unterbinden (was inzwischen möglicherweise der fall ist, das kann ich nicht genau sagen).
  2. singelton machen und vermeiden, dass die gleiche abfrage innerhalb eines requests ein zweites mal erfolgt. also nicht bloss lokal speichern, sondern innerhalb der singleton.
  3. die abfrage auf elemente begrenzen, die man zurückgeben lassen will. overhead entsteht nicht ausschliesslich bei der query-optimierung, sondern erheblich durch den datentransfer.
  4. die rückgabe auf die benötigte anzahl records begrenzen (vgl. obige position).
  5. ggf. die resultate cachen.
der angesprochene overhead durch den funtkionsaufruf selber ist zwar prinzipiell tatsächlich vorhanden. die ausführung einer funktion ist weniger performant, als das ansprechen einer variable. und diese weniger als wenn nur eine referenz verwaltet wird. allerdings lohnt es sich kaum, an dieser stelle zu optimieren; dieser anteil ist verschwindend klein im vergleich zur kommunikation mit der db. hier anzusetzen lohnt sich nur im rahmen einer kompletten überabeitung als entwicklungs-goal oder, nachdem alle übrigen optimierungen vorgenommen sind. in jedem fall lässt sich damit dann nur noch wenig gewinnen.

insgesamt muss man sich darüber klar werden, dass die abstraktion notwenigerweise auf kosten der leistung geht. dafür gewinnt man entwicklungsgeschwindigkeit. hier müsste man folgerichtig zwischen frontend und backend unterscheiden. die massgebliche systemlast wird entweder im frontend entstehen oder dann spielt leistung keine rolle, da kaum besucher da sind. will man an dieser stelle maximale leistung, wird man mit vorteil die abstraktion reduzieren und die abfragen optimieren.

Re: Performance und saubere Programmierung

Verfasst: Mi 5. Jan 2011, 10:06
von rethus
Ok, danke für deine ausführliche Antwort. Demanch war ich wohl auf den richtigen Dampfer.
Neu war mir das mit dem Singelton, da müsste ich mich mal mit beschäftigen. Du meinst damit soetwas wie hier beschrieben: http://www.net-developers.de/blog/2008/ ... n-pattern/ ?

Ich meine klar ist, dass Performance bei der Bearbeitung von Daten im Backend solange zweitrangig ist, wie die Arbeitsgeschwindigkeit nicht so stark abnimmt, dass es wertvolle Arbeitszeit der Sachbearbeiter kostet.
Dennoch denke ich, wäre es vorteilhaft, hier direkt einen sauberen und performanten Code zu generieren - gerade bei solchen Kleinigkeiten - da dies ja in dem vorliegenden Fall sowohl Entwicklungszeit (durch weniger Codezeilen) als auch Nutzer-Performance sparen würde.

Wenn das Gefühl für sauberen und Performanten Code bei den Entwicklern in Fleisch und Blut übergegangen ist, wäre das eine Baustelle weniger, die irgend wann eventuell mal nachgeflegt werden muss... und wer so ein Backend Programmiert, wird auch so im Frontend Programmieren, denn ich würde keinen kennen, der wirklich bewusst 2 Programmierstiele nutzt ;)

Naja, muss schon zugeben, dass ich auch länger als 10 Jahre im Bereich der Webprogrammierung unterwegs bin, und erst mittelfristig mit dem Profilen von Scripten begonnen habe. Denke aber mittlerweile, dass dies ebenso wie Thema sicheres Programmieren zu den Basics gehören sollte.

Eine Sache die mir immer wieder auffällt, das selbst im Corecode - in Bezug auf Code-Lesbarkeit - nur wenige Regeln eingehalten werden. Einfaches Beispiel - Programm-Block Klammern " { } ". Mal direkt hinter der Schleifenanweisung, mal in nächster Zeile... oder einschübe von Variablen usw.
Hier wäre es ne klasse Sache, wenn man sich hier auf ein Schema einigen würde... Kleinigkeiten die unterm Strich jedem Entwickler einiges an Zeit sparen können.
Idealer Startpunkt für sauberen code wäre dies hier: http://pear.php.net/manual/de/standards.php

Re: Performance und saubere Programmierung

Verfasst: Mi 5. Jan 2011, 14:18
von kummer
rethus hat geschrieben:Neu war mir das mit dem Singelton, da müsste ich mich mal mit beschäftigen. Du meinst damit soetwas wie hier beschrieben: http://www.net-developers.de/blog/2008/ ... n-pattern/ ?
yep.
rethus hat geschrieben:Ich meine klar ist, dass Performance bei der Bearbeitung von Daten im Backend solange zweitrangig ist, wie die Arbeitsgeschwindigkeit nicht so stark abnimmt, dass es wertvolle Arbeitszeit der Sachbearbeiter kostet. Dennoch denke ich, wäre es vorteilhaft, hier direkt einen sauberen und performanten Code zu generieren - gerade bei solchen Kleinigkeiten - da dies ja in dem vorliegenden Fall sowohl Entwicklungszeit (durch weniger Codezeilen) als auch Nutzer-Performance sparen würde.
performance ist immer wichtig. es gilt es halt gegen abstraktion abzuwägen. ein hoher abstraktionsgrad wird stets die leistung reduzieren. das ist letztlich unvermeidlich. und deshalb wäre im fronend der verzicht auf die high level api zu überlegen (zu gunsten der performance).
rethus hat geschrieben:und wer so ein Backend Programmiert, wird auch so im Frontend Programmieren, denn ich würde keinen kennen, der wirklich bewusst 2 Programmierstiele nutzt ;)
wer's einfach haben will, wird überall und immer high-level apis nutzen. und wird nie performanten code schreiben. wer die sache gut machen will, wird im einzelfall wählen, was aufgrund der anforderungen gefragt ist. und diese sind im backend und im frontend nicht gleich. und ein geringerer abstraktionsgrad heisst ja nicht null. geringer = weniger & weniger >= null. der einsatz unterschiedlicher abstraktionsgrade ist im übrigen durchaus normal. massgabe bilden u.a. die aktualisierungshäufigkeit, max. entwicklungsdauer (verfügbar) sowie die systemlast und die erforderliche ausführungsgeschwindigkeit. das kann durchaus auch mal zum einsatz sogar unterschiedlicher technologien führen, mindestens jedoch zum einsatz unterschiedlicher abstraktionsgrade. immer vorausgesetzt, dass die systemleistung ein entwicklungsziel darstellt. was mindestens in der vergangenheit bei contenido offenkundig nicht der fall war, und - nebenbei bemerkt - auch nicht sein muss. im bestehenden code was massgebliches zu erreichen dürfte deshalb schwierig sein, wenn man nicht gleich die ganze architektur an die hand nehmen will. ich würde mich deshalb auf die dinge fokussieren, die schnell einen erhebliche verbesserung bringen.

Re: Performance und saubere Programmierung

Verfasst: Fr 5. Apr 2013, 11:49
von Faar
Ich finde, auch das Backend sollte perfomand sein, denn die Zeiten als einmal im Monat im Backend was gemacht wurde, sind vorbei.
Jetzt ist das Backend zum Aufenthaltsplatz mehrerer Mitarbeiter geworden die alles andere gebrauche können als ein lahmes Backend.

Abstraktion und Normalisierung sind ganz nett, aber damit würde ich ein hochperformandes Kundenprojekt völlig in die Knie zwingen.
Was natürlich nicht heißt, dass es für andere Aufgaben nicht Sinn machen würde.

Re: Performance und saubere Programmierung

Verfasst: Fr 5. Apr 2013, 15:08
von xmurrix
Man sollte dabei auch berücksichtigen, das man nicht auf Teufel komm raus alles auf Performance trimmen sollte.

Ob wir z. B. $db->f('') einmal verwenden oder 20 mal, der Unterschied ist in der Summe gering. Das sind Mikrosekunden, was vernachlässigbar ist.

Ein manuell zusammengebautes SQL Statement das über mehrere Tabellen geht, ist viel schneller als die Verwendung der Generic DB Klassen. Wir haben aber auch festgestellt, dass manuell generierte SQL Statements in den letzten Jahren häufig Probleme beim Warten/Erweitern von Code verursacht haben. Da ist es wohl besser, etwas auf die Performance zu verzichten und z. B. auf generic DB Klassen umzusteigen. Im Gegenzug solle man auch nicht immer sämtliche Datensätze aus den Tabellen auslesen, dafür eher mit Pager arbeiten, z. B. 20er Schritten.
Es gibt einige Stellen, die werden auch so bleiben, die noch mit manuell erzeugtem SQL arbeiten, was wegen der Performanz wichtig ist.

Am Ende sollte es immer eine Entscheidung sein die sowohl Performanz als auch Wartbarkeit/Erweiterbarkeit berücksichtigt. Die Anzahl der Abfragen, die an die Datenbank gehen, wurden z. B. erheblich reduziert. Ich habe aktuell keine Zahlen im Kopf, aber es ist im Vergleich zur 4.8 um einiges weniger...

Beispiel:
Während früher z. B. der Mandant an verschiedenen Stellen mehrmals aus der Datenbank mit folgendem SQL-Statement geholt wurde:

Code: Alles auswählen

SELECT * FROM con_clients WHERE idclient = 1
wird nun vermehrt folgendes gemacht:

Code: Alles auswählen

$oClient = new cApiClient(1);
Während die SQL Version zwar auch auf Datenbankebene cached und das gecachte Ergebnis liefert, wird bei der zweiten Version auf Applikationsebene gecached, was viel performanter ist. Wir müssen nicht über den Datenbank-Adapter direkt an die DB gehen, das regeln wir schon viel vorher...

Man sollte eine Abstraktion nicht von vorherein verteufeln. Wenn man verstanden hat, wie das ganze funktioniert, und was es für Vorteile bringt, kann es sehr hilfreich sein.

Summa summarum ist die Performance im Backend, und auch im Frondend trotz vermehrtem Einsatz von Abstraktion um einiges besser geworden.

In 4.9 wurde da einiges gemacht und ich bin mir sicher, dass noch einiges mehr möglich ist.

Gruß
xmurrix

Re: Performance und saubere Programmierung

Verfasst: Fr 5. Apr 2013, 18:24
von Spider IT
xmurrix hat geschrieben:Ein manuell zusammengebautes SQL Statement das über mehrere Tabellen geht, ist viel schneller als die Verwendung der Generic DB Klassen.
Ein SQL-Statement, welches mehrere Tabellen mit einander verknüpft, verursacht in der Datenbank eine versuchte Verknüpfung ALLER Datensätze mit einander.
Bei zwei Tabellen mit jeweils 100 Datensätze wird dann also aus 10.000 Datensätze (100 x 100) gefiltert.
Bei 3 Tabellen mit jeweils 1.000 Datensätze sind das schon 1.000.000.000 Datensätze, das kostet dann schon merkbar Zeit.
Wenn also mehrere Tabellen mit viele Datensätze verknüpft werden sollen, ist es schneller eine Abfrage über die Haupttabelle zu machen, das Ergebnis evtl. in ein Array zu speichern (oder mit eine zweite DB-Klasseninstanz weiter zu machen), und dann für jeden Datensatz (eine) weitere Abfrage(n) auszuführen.

Gruß
René

Re: Performance und saubere Programmierung

Verfasst: Fr 5. Apr 2013, 20:55
von xmurrix
Spider IT hat geschrieben:...Ein SQL-Statement, welches mehrere Tabellen mit einander verknüpft, verursacht in der Datenbank eine versuchte Verknüpfung ALLER Datensätze mit einander.
Bei zwei Tabellen mit jeweils 100 Datensätze wird dann also aus 10.000 Datensätze (100 x 100) gefiltert.
Hier ein Thema bei stackoverflow zum Thema "multiple select vs. join":
http://stackoverflow.com/questions/1067 ... le-queries

Man kann das nicht verallgemeinern, es hängt immer davon ab, wie man sein Abfrage aufbaut und wie die DB Struktur aufgebaut ist...

Gruß
xmurrix

Re: Performance und saubere Programmierung

Verfasst: Fr 5. Apr 2013, 22:05
von Spider IT
Hab ich gelesen, hier mal ein paar Ausschnitte:
left joins use exponentially more memory with redundant data.

The memory limit might not be as bad if you only do a join of two tables, but generally three or more and it becomes worth different queries.
Woran das wohl liegt, vielleicht an der Unmenge an Datensätze?
In my experience I have found it's usually faster to run several queries, especially when retrieving large data sets.

When interacting with the database from another application, such as PHP, there is the argument of one trip to the server over many.
Entscheidend ist die Verbindung, die aufzubauen kostet mehr Zeit als ein paar Abfragen durchzuführen.
In my experience people are often mislead by the "fewer database round-trips" argument when in reality on most OLTP systems where the database is on the same LAN, the real bottleneck is rarely the network.
Brauchst du Hilfe bei der Übersetzung? ;)

Ich schreibe SQL-Anweisungen mit allen möglichen JOINs ohne Hilfsmittel.
Irgendwann habe ich dann selbst die Erfahrung gemacht, dass wenn "ausreichend" Daten in der Datenbank vorhanden sind (komischerweise ist dies bei der Entwicklung nie der Fall), die Anwendung durch solch eine JOIN-Abfrage (oder sonstwie verknüpft), stark in die Knie geht.
Und das war eine Abfrage über "nur" 3 Tabellen...
xmurrix hat geschrieben:Man kann das nicht verallgemeinern
Das hast du aber doch hier schon gemacht:
xmurrix hat geschrieben:Ein manuell zusammengebautes SQL Statement das über mehrere Tabellen geht, ist viel schneller als die Verwendung der Generic DB Klassen.
Und ich habe gesagt dass das bei zwei Tabellen mit wenige Daten noch stimmen mag, aber darüber hinaus nicht mehr stimmt.
Gerade bei mehrere Tabellen mit viele Daten liegt der Zeitvorteil klar bei mehrere getrennte Abfragen.
Das ist keine Verallgemeinerung, sondern beschreibt unterschiedliche Fälle.

Gruß
René

Re: Performance und saubere Programmierung

Verfasst: Fr 5. Apr 2013, 22:15
von xmurrix
@Spider-IT
Das wird mir hier zu kindisch, ich hätte von einem, der 20 Jahre Erfahrung hat, etwas mehr Professionalität erwartet, sowohl im technischen als auch auch im zwischenmenschlichen Bereich. Anscheinend fehlt es hierbei an beidem, was wirklich sehr schade ist.

Ansonsten kann ich mir nicht erklären, warum hier jedes mal versucht wird, immer das letzte Wort zu haben oder auf irgendwelchen Themen herumgehackt wird.

Aber belassen wir es dabei, ich habe wirklich keine Zeit für sowas...

Re: Performance und saubere Programmierung

Verfasst: Sa 6. Apr 2013, 07:08
von Oldperl
@Murat: Ich finde nicht das René hier "kindisch" agiert hat. Eigentlich hat er recht sachlich deine Argumentation zerlegt. Ich denke hier sollten alle mal wieder ein wenig vom Gas gehen und persönliche Angriffe aus sachlichen Diskussionen raus lassen. Das ihr euch nicht "riechen" könnt wissen inzwischen alle.
xmurrix hat geschrieben:
Spider IT hat geschrieben:Man kann das nicht verallgemeinern, es hängt immer davon ab, wie man sein Abfrage aufbaut und wie die DB Struktur aufgebaut ist...
Das ist so, da kann man eigentlich nicht widersprechen. Aber es kommt, so wie René sagt, selbstverständlich auch darauf an, wie meine Daten und meine DB-Strukturen aussehen. Hier fehlen leider oft die Rückmeldungen aus der Praxis, oder die Zeit diese umzusetzen.
Grundsätzlich muss man aber auch sagen, das Contenido durch seine vielen "Köche" bereits ziemlich "vermurkst" ist/wurde. Hier fehlt und fehlte es in der Vergangenheit an einem klaren Konzept und entsprechenden Vorgaben. Und um dieses wieder gerade zu rücken benötigt man einheitliche Standards, Vorgaben und vor allem ein vernünftiges Testing, nach Möglichkeit mit praxisnahen Daten.

Gruß aus Franken

Ortwin

Re: Performance und saubere Programmierung

Verfasst: Sa 6. Apr 2013, 16:41
von Faar
Ich hatte zum Beispiel aus anderen Nichtcontenidoprojekten Tabellen mit ca. 40 Millionen Einträgen und dort sind schon schlichte Abfragen ein Risiko, weil man mit den mehrere Gigabyte großen Tabellen schnell den RAM des Datenbank-Servers sprengt.
Seit längerem bekomme ich eine Tabelle mit nur 14 Millionen Einträgen nicht mehr in den Griff, weil bei dem Versuch, ältere Einträge zu löschen (DELETE), gleich die Datenbank für eine Weile still steht. Der DELETE Befehl einzelner Zeilen löst intern sowas wie eine Sortierung aus, während TRUNCATE und DROP sehr schnell arbeiten. Da funktionieren nur einfach Abfragen, sprich, die Tabelle muss auch so aufgebaut sein, dass sehr einfach Abfragen möglich sind.
Notfalls wird zur Laufzeit eine VIEW von einem Datenausschnitt erzeugt und diese dann sortiert abgefragt und danach wieder gelöscht.

Von daher sind mir solche SQL-Konstrukte wie ich sie Contenido vorfinde, eher fremd.
Und ich vermute, dass Projekte mit so großen Datenmengen mit Contenido 4.8 nicht funktionieren würden.
Allerdings wird es schwieriger bis fast unmöglich, später irgendetwas an den Tabellen zu verändern, weil jede SQL und Funktion überarbeitet werden müssten. Da rächt sich die Vereinfachung wieder.

Aber ich sehe Contenido auch nicht als Basis für spezielle Projekte mit großen Datenmengen, und wenn, dann würde ich die Tabellen außerhalb von Contenido anlegen und verwalten.
Wie sich das mit Contenido 4.9 verhält, weiß ich noch nicht.
Bei der 4.8 fiel mir auf, dass z.B. die Menü-Abfrage recht aufwändig ist.
Ich denke halt, dass sich einiges in den letzten 10 Jahren verändert hat und von daher die Datenbank-Architektur vielleicht nicht mehr ganz günstig ist.
Heute kann ich tausend einfache Abfragen nacheinander schicken, das bleibt zusammen alles im kleinsten Bereich, während man früher dachte, man müsse möglichst alles in eine Abfrage packen. Selbst Tabellen mit Millionen Einträgen sind kein Problem, solange keine Joins drin sind und die Datenbank nicht die ganze Tabelle sortieren muss.
Das kommt bei üblichen Contenido Projekten eher selten vor und sowohl Datenbank wie MySQL als auch die Webserver werden immer schneller, so dass immer ein Ausgleich statt findet. Trotzdem schadet es nicht, zu schauen wo man etwas überarbeiten kann. Das Bessere ist immer des Guten Feind.

Nur verstehe ich nicht ganz, was das mit mehr oder weniger sauberer Programmierung zu tun hat?
Ich denke, man programmiert bedarfsgerecht, das eine so und das andere so.

Re: Performance und saubere Programmierung

Verfasst: Sa 6. Apr 2013, 17:02
von Spider IT
Faar hat geschrieben:Nur verstehe ich nicht ganz, was das mit mehr oder weniger sauberer Programmierung zu tun hat?
Ich denke, man programmiert bedarfsgerecht, das eine so und das andere so.
In ein Projekt sollte durchgängig die gleiche Art der Programmierung genutzt werden und nicht an 10 Stellen 10 verschiedene Arten, da damit das Projekt einfach nicht mehr wartbar ist.
Klar kann jeder seine eigene Programmierung überarbeiten, aber später ist einer der Programmierer mal nicht mehr da, und dann ändert ein anderer dessen Programmierung ab, aber auf seine Art, und dann noch später wieder ein anderer...
Ich denke das Problem ist damit klar, oder? ;)

Gruß
René

Re: Performance und saubere Programmierung

Verfasst: Mo 8. Apr 2013, 10:37
von Faar
Ich meinte die Aussage "Ich denke, man programmiert bedarfsgerecht, das eine so und das andere so." etwas anders, eher so dass man unterscheiden sollte, wo braucht man wirklich schnelle direkte Abfragen um Engpässe zu verhindern und wo kann man es nach den Standard bauen.
Oder besser gesagt, man setzt Standards auf die besagen, hier bauen wir den Code so und hier so, zwei Regeln, nicht Zehn.

Überhaupt sind Standards wichtig und gut, hab selbst mal ein wirklich dickes Standard-Werk aufgesetzt, aber dabei fiel mir auf, dass auch Standards oft nur Schall und Rauch sind, weil jede neue Erkenntnis einen Standard zum alten Eisen oder sogar Klotz am Bein werden lassen kann. Die Entwicklung geht weiter und die Anforderungen auch und jeder Standard muss ebenso immer hinterfragt werden, ob er noch passt.
Und Standards sind auch nicht das Allheilmittel gegen unsaubere Programmierung, weil Standards die von den Leuten nicht angenommen werden weil sie mehr hindern als nützen, kann man eigentlich auch gleich bleiben lassen :( oder sich wie ein Diktator aufführen (als Chef der Truppe natürlich :evil: ).
Flöhe hüten stelle ich mir leichter vor als eine Truppe junger Entwickler die voller Tatendrang schnelle Ergebnisse haben wollen (ich sprech von letzterem aus Erfahrung :roll: ).

Nicht wartbar wird ein Code auch dadurch, dass zu hohe Hürden gesteckt wurden, um überhaupt noch irgendwo ein Feature einzubauen.
Oder dass einmal Standards gesetzt wurden und später neue Anforderungen kommen die vorher nicht angedacht waren und man die Standards aber nicht antasten will.

Ich versuche eher so einen Mittelweg zu finden zwischen "Wir machen jetzt alles neu oder anders!" und "Wir machen genauso wie geplant und haben es immer so gemacht!".
Wenn man die Erfolgsgeschichte von WhatsApp anschaut, scheinbar (oder offensichtlich?) lausig programmiert und nachträglich am verbessern aber einen riesen Erfolg.
Wahrscheinlich stünden sie noch besser da, wenn gleich von Anfang an einer (mit genug Erfahrung in solchen Projekten) gesagt hätte, wie grundsätzlich die Regeln sind.
Aber vielleicht wärs auch schief gegangen, weil sie zu lange gebraucht hätten, um Ergebnisse zu liefern?
Was ist da nun richtig: Von Anfang an gleich den richtigen (?) Rahmen aufzubauen oder schnellere Ergebnisse liefern?

Ich habe auch ein reines Spaghetticode Projekt am Hals, früher über 2000 Dateien schwer und heute viel kürzer, u.a. dank meiner Arbeit.
Das Problem der nicht mehr Wartbarkeit kenne ich nur zu gut und auch hier waren schon viele Leute am Werk und jeder so wie er wollte.
Dokumentation ist wie immer nicht vorhanden und geschätzt 70 oder 80 Prozent der Arbeitszeit für dieses Projekt nutze ich für Refaktorierung, anstatt neues zu entwickeln.
Das Problem ist der Kunde, der nicht ganz versteht dass man auf morsches Gebälk nicht einfach ein Stockwerk mehr drauf packen kann, also neue Features.
Und Spaghetticode üblich, funktionierte an vielen verschiedenen Stellen etwas plötzlich nicht mehr, wenn ich ganz woanders etwas überarbeite.
Das heißt, was aus Kundensicht eigentlich eine Kleinigkeit sein sollte, ist in Wirklichkeit oft eine Mammutaufgabe.

Aber bei "sauberer Programmierung" kann es ähnlich laufen, wenn man feststellt, dass die ganze Sache überarbeitet werden muss.
Dann tut sich nach außen hin lange nichts, weil intern sehr viel umgearbeitet werden muss.

Re: Performance und saubere Programmierung

Verfasst: Mo 8. Apr 2013, 18:19
von Oldperl
Ich denke gewisse Regeln sind ein Muss bei jedem guten Projekt. Dazu gehören für mich Coding Conventions und ebenso gewisse Standards und Vorgaben. Was IMO zumeist vernachlässigt wird, und dadurch spätere Erweiterung und Wartung erschwert, ist die Dokumentation, gerade bei größeren Projekten. Quick & Dirty ist eine Sache, aber das heißt IMO nicht einfach drauflosstricken ohne Hand und Fuss.
ABer ich denke das geht hier momentan eher in eine philosophische (Grundsatz-)Diskussion über an der ich mich nicht unbedingt weiter beteiligen will. Das Projektmanagement bei Contenido hat sich da teilweise schon gebessert, könnte aber in vielen Bereichen noch weitere Änderungen vertragen. Aber das soll nicht mehr meine Baustelle sein.

Gruß aus Franken

Ortwin