aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Eggert2018-08-20 15:52:29 -0700
committerPaul Eggert2018-08-20 16:01:31 -0700
commiteb83344fc7c08ec08b51e7700f1ac2632afa462c (patch)
tree69c96f89acf42957ed98dc7ce189cb49d04abecd /src
parent36de7bd7b0b9fcd038c440b4705e9186bfbaaa41 (diff)
downloademacs-eb83344fc7c08ec08b51e7700f1ac2632afa462c.tar.gz
emacs-eb83344fc7c08ec08b51e7700f1ac2632afa462c.zip
Speed up (nthcdr N L) when L is circular
Also, fix bug when N is a positive bignum, a problem reported by Eli Zaretskii and Pip Cet in: https://lists.gnu.org/r/emacs-devel/2018-08/msg00690.html * src/fns.c (Fnthcdr): If a cycle is found, reduce the count modulo the cycle length before continuing. This reduces the worst-case cost of (nthcdr N L) from N to min(N, C) where C is the number of distinct cdrs of L. Reducing modulo the cycle length also allows us to do arithmetic with machine words instead of with GMP. * test/src/fns-tests.el (test-nthcdr-circular): New test.
Diffstat (limited to 'src')
-rw-r--r--src/fns.c58
1 files changed, 52 insertions, 6 deletions
diff --git a/src/fns.c b/src/fns.c
index aeb9308d227..8cff6b1b6ca 100644
--- a/src/fns.c
+++ b/src/fns.c
@@ -1403,7 +1403,12 @@ DEFUN ("nthcdr", Fnthcdr, Snthcdr, 2, 2, 0,
1403 (Lisp_Object n, Lisp_Object list) 1403 (Lisp_Object n, Lisp_Object list)
1404{ 1404{
1405 CHECK_INTEGER (n); 1405 CHECK_INTEGER (n);
1406 Lisp_Object tail = list; 1406
1407 /* A huge but in-range EMACS_INT that can be substituted for a
1408 positive bignum while counting down. It does not introduce
1409 miscounts because a list or cycle cannot possibly be this long,
1410 and any counting error is fixed up later. */
1411 EMACS_INT large_num = EMACS_INT_MAX;
1407 1412
1408 EMACS_INT num; 1413 EMACS_INT num;
1409 if (FIXNUMP (n)) 1414 if (FIXNUMP (n))
@@ -1412,16 +1417,57 @@ DEFUN ("nthcdr", Fnthcdr, Snthcdr, 2, 2, 0,
1412 { 1417 {
1413 num = mpz_sgn (XBIGNUM (n)->value); 1418 num = mpz_sgn (XBIGNUM (n)->value);
1414 if (0 < num) 1419 if (0 < num)
1415 num = EMACS_INT_MAX; /* LIST cannot possibly be this long. */ 1420 num = large_num;
1416 } 1421 }
1417 1422
1418 for (; 0 < num; num--) 1423 EMACS_INT tortoise_num = num;
1424 Lisp_Object tail = list, saved_tail = tail;
1425 FOR_EACH_TAIL_SAFE (tail)
1419 { 1426 {
1420 if (! CONSP (tail)) 1427 if (num <= 0)
1428 return tail;
1429 if (tail == li.tortoise)
1430 tortoise_num = num;
1431 saved_tail = XCDR (tail);
1432 num--;
1433 rarely_quit (num);
1434 }
1435
1436 tail = saved_tail;
1437 if (! CONSP (tail))
1438 {
1439 CHECK_LIST_END (tail, list);
1440 return Qnil;
1441 }
1442
1443 /* TAIL is part of a cycle. Reduce NUM modulo the cycle length to
1444 avoid going around this cycle repeatedly. */
1445 intptr_t cycle_length = tortoise_num - num;
1446 if (! FIXNUMP (n))
1447 {
1448 /* Undo any error introduced when LARGE_NUM was substituted for
1449 N, by adding N - LARGE_NUM to NUM, using arithmetic modulo
1450 CYCLE_LENGTH. */
1451 mpz_t z; /* N mod CYCLE_LENGTH. */
1452 mpz_init (z);
1453 if (cycle_length <= ULONG_MAX)
1454 num += mpz_mod_ui (z, XBIGNUM (n)->value, cycle_length);
1455 else
1421 { 1456 {
1422 CHECK_LIST_END (tail, list); 1457 mpz_set_intmax (z, cycle_length);
1423 return Qnil; 1458 mpz_mod (z, XBIGNUM (n)->value, z);
1459 intptr_t iz;
1460 mpz_export (&iz, NULL, -1, sizeof iz, 0, 0, z);
1461 num += iz;
1424 } 1462 }
1463 mpz_clear (z);
1464 num += cycle_length - large_num % cycle_length;
1465 }
1466 num %= cycle_length;
1467
1468 /* One last time through the cycle. */
1469 for (; 0 < num; num--)
1470 {
1425 tail = XCDR (tail); 1471 tail = XCDR (tail);
1426 rarely_quit (num); 1472 rarely_quit (num);
1427 } 1473 }