php - recorrer - isset() vs strlen()-un cálculo de longitud de cadena rápida/clara
strlen php (8)
Cualquier diferencia de velocidad en esto no tiene absolutamente ninguna consecuencia. Serán unos pocos milisegundos en el mejor de los casos.
Use el estilo que mejor le sea legible a usted y a cualquier otra persona que trabaje en el código: personalmente votaría con firmeza el segundo ejemplo porque, a diferencia del primero, hace que la intención (verificar la longitud de una cadena) sea absolutamente clara.
Encontré este código ...
if(isset($string[255])) {
// too long
}
isset () es entre 6 y 40 veces más rápido que
if(strlen($string) > 255) {
// too long
}
El único inconveniente para el isset () es que el código no está claro, no podemos decir de inmediato lo que se está haciendo (ver la respuesta de pekka). Podemos envolver isset () dentro de una función, es decir, strlt ($ string, 255) pero luego perdemos los beneficios de velocidad de isset ().
¿Cómo podemos usar la función isset () más rápida al tiempo que conservamos la legibilidad del código?
EDITAR: prueba para mostrar la velocidad http://codepad.org/ztYF0bE3
strlen() over 1000000 iterations 7.5193998813629
isset() over 1000000 iterations 0.29940009117126
EDIT2: he aquí por qué isset () es más rápido
$string = ''abcdefg'';
var_dump($string[2]);
Output: string(1) “c”
$string = ''abcdefg'';
if (isset($string[7])){
echo $string[7].'' found!'';
}else{
echo ''No character found at position 7!'';
}
Esto es más rápido que usar strlen () porque, "... llamar a una función es más costoso que usar una construcción de lenguaje". http://www.phpreferencebook.com/tips/use-isset-instead-of-strlen/
EDIT3: Siempre me enseñaron a interesarme por la optimización de mirco. Probablemente porque me enseñaron en un momento en que los recursos en las computadoras eran muy pequeños. Estoy abierto a la idea de que puede no ser importante, hay algunos buenos argumentos en contra en las respuestas. Empecé una nueva pregunta para explorar esto ... https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding
De acuerdo, realicé las pruebas, ya que no podía creer que el método isset () fuera más rápido, pero sí, y considerablemente. El método isset () es consistentemente aproximadamente 6 veces más rápido.
He intentado con cadenas de varios tamaños y ejecutando una cantidad variable de iteraciones; las proporciones siguen siendo las mismas, y también la longitud total de ejecución por cierto (para cadenas de tamaños variables), porque tanto isset () como strlen () son O (1) (lo cual tiene sentido, isset solo necesita hacer una búsqueda en una matriz C y strlen () solo devuelve el conteo de tamaño que se conserva para la cadena).
Lo busqué en la fuente php, y creo que entiendo por qué. isset (), debido a que no es una función sino un constructo de lenguaje, tiene su propio código de operación en la máquina virtual de Zend. Por lo tanto, no necesita buscarse en la tabla de funciones y puede realizar un análisis de parámetros más especializado. El código está en zend_builtin_functions.c para strlen () y zend_compile.c para isset (), para aquellos interesados.
Para relacionar esto con la pregunta original, no veo ningún problema con el método isset () desde un punto de vista técnico; pero es más difícil de leer para las personas que no están acostumbradas al idioma. Además, el método isset () será constante en el tiempo, mientras que el método strlen () será O (n) al variar la cantidad de funciones compiladas en PHP. Es decir, si compila PHP y compila estáticamente en muchas funciones, todas las llamadas a funciones (incluyendo strlen ()) serán más lentas; pero isset () será constante. Sin embargo, esta diferencia en la práctica será insignificante; Tampoco sé cuántas tablas de punteros de función se mantienen, por lo que si las funciones definidas por el usuario también tienen una influencia. Me parece recordar que están en una mesa diferente y, por lo tanto, son irrelevantes para este caso, pero ha pasado un tiempo desde la última vez que trabajé con esto.
Por lo demás, no veo ningún inconveniente para el método isset (). No sé de otras maneras de obtener la longitud de una cuerda, cuando no se consideran las intrincadas, como el recuento de explosión + y cosas por el estilo.
Finalmente, también probé tu sugerencia anterior de envolver isset () en una función. Esto es más lento que incluso el método strlen () porque necesita otra llamada a función y, por lo tanto, otra búsqueda de tabla hash. La sobrecarga del parámetro adicional (para el tamaño a verificar) es insignificante; como es la copia de la cadena cuando no se pasa por referencia.
El inconveniente es que isset no es explícito en absoluto, mientras que strlen es muy claro acerca de cuál es su intención. Si alguien lee tu código y tiene que entender lo que estás haciendo, podría molestarlo y no ser muy claro.
A menos que esté ejecutando Facebook, dudo que ese será el lugar donde su servidor gastará la mayoría de sus recursos, y debe seguir usando strlen.
Acabo de probar strlen es mucho más rápido que el isset.
0.01 seconds for 100000 iterations with isset
0.04 seconds for 100000 iterations with strlen
Pero no cambia lo que dije justo ahora.
La secuencia de comandos como algunas personas acaban de preguntar:
$string = ''xdfksdjhfsdljkfhsdjklfhsdlkjfhsdjklfhsdkljfhsdkljfhsdljkfsdhlkfjshfljkhfsdljkfhsdkljfhsdkljfhsdklfhlkjfhkljfsdhfkljsdhfkljsdhfkljhsdfjklhsdjklfhsdkljfhklsdhfkljsdfhdjkshfjlhdskljfhsdkljfhsdjkfhsjkldhfklsdjhfkjlsfhdjkflsdhfjklfsdljfsdlkdlfkjflfkjsdfkl'';
for ($i = 0; $i < 100000; $i++) {
if (strlen($string) == 255) {
// if (isset($string[255])) {
// do nothing
}
}
En las aplicaciones web orientadas a objetos modernas, una sola línea que escriba dentro de una clase pequeña fácilmente puede ejecutarse varias veces para crear una sola página web.
Es posible que desee crear un perfil de su sitio web con XDebug y le sorprenderá cuántas veces se ejecuta cada Método de una clase.
Entonces, en escenarios del mundo real, es posible que no trabaje solo con pequeñas cuerdas, sino también con documentos realmente grandes de hasta 3MB o más.
También puede encontrar texto con caracteres no latinos.
Así que, finalmente, lo que inicialmente fue solo una pequeña pérdida de rendimiento podría dar como resultado cientos de milisegundos en una representación de página web.
Así que estoy muy interesado en este tema y escribí una pequeña prueba que probaría 4 métodos diferentes para verificar si una cadena está realmente vacía "" o realmente contiene algo como "0".
function stringCheckNonEmpty0($string)
{
return (empty($string));
}
function stringCheckNonEmpty1($string)
{
return (strlen($string) > 0);
}
function stringCheckNonEmpty1_2($string)
{
return (mb_strlen($string) > 0);
}
function stringCheckNonEmpty2($string)
{
return ($string !== "");
}
function stringCheckNonEmpty3($string)
{
return (isset($string[0]));
}
Descubrí que PHP es un momento difícil para trabajar con caracteres no latinos y copié un texto ruso de una página web para comparar los resultados entre la pequeña cadena "0" y el texto ruso más grande.
$steststring = "0"; $steststring2 = "Hotel Majestic в городе Касабланка располагается всего в нескольких минутах от " . "следующих достопримечательностей и объектов: " . "Playas Ain Diab y La Corniche и Центральный рынок Касабланки. " . "Этот отель находится вблизи следующих достопримечательностей и объектов: " . "Площадь Мухаммеда V и Культурный комплекс Сиди-Бельот.";
Para ver realmente una diferencia, llamé a cada función de prueba varios millones de veces.
$iruncount = 10000000;
echo "test: empty(/"0/"): starting .../n";
$tmtest = 0;
$tmteststart = microtime(true);
$tmtestend = 0;
for($irun = 0; $irun < $iruncount; $irun++)
stringCheckNonEmpty0($steststring);
$tmtestend = microtime(true);
$tmtest = $tmtestend - $tmteststart;
echo "test: empty(/"0/"): ''$tmtest'' s/n";
Resultados de la prueba
$ php test_string_check.php
test0.1: empty("0"): starting ...
test0.1: empty("0"): ''7.0262970924377'' s
test0.2: empty(russian): starting ...
test0.2: empty(russian): ''7.2237210273743'' s
test1.1.1: strlen("0"): starting ...
test1.1.1: strlen("0"): ''11.045154094696'' s
test1.1.2: strlen(russian): starting ...
test1.1.2: strlen(russian): ''11.106546878815'' s
test1.2.1: mb_strlen("0"): starting ...
test1.2.1: mb_strlen("0"): ''11.320801019669'' s
test1.2.2: mb_strlen(russian): starting ...
test1.2.2: mb_strlen(russian): ''23.082058906555'' s
test2.1: ("0" !== ""): starting ...
test2.1: ("0" !== ""): ''7.0292129516602'' s
test2.2: (russian !== ""): starting ...
test2.2: (russian !== ""): ''7.1041729450226'' s
test3.1: isset(): starting ...
test3.1: isset(): ''6.9401099681854'' s
test3.2: isset(russian): starting ...
test3.2: isset(russian): ''6.927631855011'' s
$ php test_string_check.php
test0.1: empty("0"): starting ...
test0.1: empty("0"): ''7.0895299911499'' s
test0.2: empty(russian): starting ...
test0.2: empty(russian): ''7.3135821819305'' s
test1.1.1: strlen("0"): starting ...
test1.1.1: strlen("0"): ''11.265664100647'' s
test1.1.2: strlen(russian): starting ...
test1.1.2: strlen(russian): ''11.282053947449'' s
test1.2.1: mb_strlen("0"): starting ...
test1.2.1: mb_strlen("0"): ''11.702164888382'' s
test1.2.2: mb_strlen(russian): starting ...
test1.2.2: mb_strlen(russian): ''23.758249998093'' s
test2.1: ("0" !== ""): starting ...
test2.1: ("0" !== ""): ''7.2174110412598'' s
test2.2: (russian !== ""): starting ...
test2.2: (russian !== ""): ''7.240779876709'' s
test3.1: isset("0"): starting ...
test3.1: isset("0"): ''7.2104151248932'' s
test3.2: isset(russian): starting ...
test3.2: isset(russian): ''7.2232971191406'' s
Conclusión
- La función convencional
emtpy()
funciona bien pero falla en cadenas como "0". - La función
mb_strlen()
que es necesaria para verificar textos con caracteres no latinos tiene un peormb_strlen()
en textos más grandes. - ¡Check
$string !== ""
funciona muy bien. Incluso mejor que la funciónempty()
. - Pero el mejor rendimiento da el
isset($string[0])
Check.
Definitivamente tendré que trabajar sobre toda la Biblioteca de objetos.
En primer lugar, quiero apuntar hacia una respuesta de Artefacto que explique por qué las llamadas a funciones tienen una sobrecarga sobre los constructos del lenguaje.
En segundo lugar, quiero hacerte consciente del hecho de que XDebug reduce en gran medida el rendimiento de las llamadas a funciones, por lo que si estás ejecutando XDebug puedes obtener números intrincados. Referencia (segunda sección de la pregunta). Por lo tanto, en producción (donde es de esperar que no tenga instalado XDebug), la diferencia es aún menor. Baja de 6x a 2x.
En tercer lugar, debe saber que, aunque hay una diferencia medible, esta diferencia solo aparece si este código se ejecuta en un ciclo cerrado con millones de iteraciones. En una aplicación web normal, la diferencia no será medible, se hundirá en el ruido de la varianza.
En cuarto lugar, tenga en cuenta que hoy en día el tiempo de desarrollo es mucho más caro que la carga del servidor. Un desarrollador que gasta incluso solo medio segundo más entendiendo lo que hace el código isset es mucho más costoso que el ahorro en la carga de la CPU. Además, la carga del servidor se puede ahorrar mucho mejor mediante la aplicación de optimizaciones que realmente marcan la diferencia (como el almacenamiento en caché).
Si quieres mantener la claridad, podrías hacer algo como:
function checklength(&$str, $len)
{
return isset($str[$len]);
}
Tu código esta incompleto
Aquí, lo arreglé por ti:
if(isset($string[255])) {
// something taking 1 millisecond
}
vs
if(strlen($string) > 255) {
// something taking 1 millisecond
}
Ahora no tienes un bucle vacío, sino uno realista. Consideremos que toma 1 milisegundo para hacer algo.
Una CPU moderna puede hacer muchas cosas en 1 milisegundo, eso se da. Pero las cosas como el acceso aleatorio al disco duro o una solicitud de base de datos tardan varios milisegundos, lo que también es un escenario realista.
Ahora vamos a calcular tiempos nuevamente:
realistic routine + strlen() over 1000000 iterations 1007.5193998813629
realistic routine + isset() over 1000000 iterations 1000.29940009117126
¿Ver la diferencia?
esta es la última prueba:
function benchmark_function($fn,$args=null)
{
if(!function_exists($fn))
{
trigger_error("Call to undefined function $fn()",E_USER_ERROR);
}
$t = microtime(true);
$r = call_user_func_array($fn,$args);
return array("time"=>(microtime(true)-$t),"returned"=>$r,"fn"=>$fn);
}
function get_len_loop($s)
{
while($s[$i++]){}
return $i-1;
}
echo var_dump(benchmark_function("strlen","kejhkhfkewkfhkwjfjrw"))."<br>";
echo var_dump(benchmark_function("get_len_loop","kejhkhfkewkfhkwjfjrw"));
Resultados devueltos:
EJECUTAR 1:
array (3) {["tiempo"] => float (2.1457672119141E-6) ["returned"] => int (20) ["fn"] => cadena (6) "strlen"} array (3) { ["tiempo"] => float (1.1920928955078E-5) ["returned"] => int (20) ["fn"] => string (12) "get_len_loop"}
RUN 2:
array (3) {["tiempo"] => float (4.0531158447266E-6) ["returned"] => int (20) ["fn"] => cadena (6) "strlen"} array (3) { ["tiempo"] => float (1.5020370483398E-5) ["returned"] => int (20) ["fn"] => string (12) "get_len_loop"}
RUN 3:
array (3) {["tiempo"] => float (4.0531158447266E-6) ["returned"] => int (20) ["fn"] => cadena (6) "strlen"} array (3) { ["tiempo"] => float (1.2874603271484E-5) ["returned"] => int (20) ["fn"] => string (12) "get_len_loop"}
RUN 4:
array (3) {["tiempo"] => float (3.0994415283203E-6) ["returned"] => int (20) ["fn"] => cadena (6) "strlen"} array (3) { ["tiempo"] => float (1.3828277587891E-5) ["returned"] => int (20) ["fn"] => string (12) "get_len_loop"}
CORRER 5:
array (3) {["tiempo"] => float (5.0067901611328E-6) ["returned"] => int (20) ["fn"] => cadena (6) "strlen"} array (3) { ["tiempo"] => float (1.4066696166992E-5) ["returned"] => int (20) ["fn"] => string (12) "get_len_loop"}